SlideShare a Scribd company logo
MADRID · NOV 18-19 · 2016
Why Using
by @andres_viedma
instead of
in your Java Tests
Andrés ViedmaAndrés Viedma
@andres_viedma@andres_viedma
01 SPOCK?
A testing framework
Tests are written in
100% compatible with Java code
Runs on
http://spockframework.org/
A basic JUnit test
@Test
public void thatAddAPositiveNumberToZeroReturnsTheSamePositiveNumber()
throws Exception {
int positiveNumber = 23;
int zero = 0;
int result = zero + positiveNumber;
assertEquals(positiveNumber, result);
}
A basic Spock test
def "Add a positive number to zero returns the same positive number"() {
given:
def positiveNumber = 23
def zero = 0
when:
def result = zero + positiveNumber
then:
result == positiveNumber
}
A basic Spock test
def "Add a positive number to zero returns the same positive number"() {
given:
def positiveNumber = 23
def zero = 0
when:
def result = zero + positiveNumber
then:
result == positiveNumber
}
A basic Spock test
DSL
def "Add a positive number to zero returns the same positive number"() {
given:
def positiveNumber = 23
def zero = 0
when:
def result = zero + positiveNumber
then:
result == positiveNumber
}
String literals
def "Add a positive number to zero returns the same positive number"() {
(...)
}
@Test
public void thatAddAPositiveNumberToZeroReturnsTheSamePositiveNumber()
throws Exception {
(...)
}
String literals
def "Add a positive number to zero returns the same positive number"() {
(...)
}
@Test
public void thatAddAPositiveNumberToZeroReturnsTheSamePositiveNumber()
throws Exception {
(...)
}
def "En un lugar de la Mancha, de cuyo nombre no quiero acordarme,
no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero,
adarga antigua, rocín flaco y galgo corredor. "() {
(...)
}
Spock Blocks
given:
def positiveNumber = 23
def zero = 0
when:
def result = zero + positiveNumber
then:
result == positiveNumber
int positiveNumber = 23;
int zero = 0;
int result = zero + positiveNumber;
assertEquals(positiveNumber, result);
Programming vs. Specification
Spock Blocks
given:
def positiveNumber = 23
def zero = 0
when:
def result = zero + positiveNumber
then:
result == positiveNumber
// Given
int positiveNumber = 23;
int zero = 0;
// When
int result = zero + positiveNumber;
// Then
assertEquals(positiveNumber, result);
Compilation check
Programming vs. Specification
Documented Spock Blocks
given: "A positive number"
def positiveNumber = 23
and: "Well... a SPECTACULAR zero"
def zero = 0
when: "The number is added to zero"
def result = zero + positiveNumber
then: "The result is the same number"
result == positiveNumber
int positiveNumber = 23;
int zero = 0;
int result = zero + positiveNumber;
assertEquals(positiveNumber, result);
Documents purpose
Helps thinking
expect block
given: "A positive number"
def positiveNumber = 23
expect: "That added to zero results the same number"
zero + positiveNumber == positiveNumber
Replaces when-then for simple
functional tests
Expectations (then / expect)
given:
def positiveNumber = 23
def zero = 0
when:
def result = zero + positiveNumber
then:
result == positiveNumber
int positiveNumber = 23;
int zero = 0;
int result = zero + positiveNumber;
assertEquals(positiveNumber, result);
Multiple expectations
then:
result == positiveNumber
!collection.isEmpty()
then:
result == positiveNumber
and:
!collection.isEmpty()
Multiple expectations
then:
result == positiveNumber
!collection.isEmpty()
then:
def expectedResult = positiveNumber
result == expectedResult
Only conditions and variable
assignments allowed
then:
result == positiveNumber
and:
!collection.isEmpty()
def "Crash if zero"() {
when:
object.crashIfZero(0)
then:
thrown(ZeroException)
}
@Test(expected = OperationException.class)
public void crashIfZero()
throws Exception {
object.crashIfZero(0);
}
Exception conditions
def "Crash if zero"() {
when:
object.crashIfZero(0)
then:
thrown(ZeroException)
}
@Test(expected = OperationException.class)
public void crashIfZero()
throws Exception {
object.crashIfZero(0);
}
def "Dont crash if not zero"() {
when:
object.crashIfZero(1)
then:
notThrown(ZeroException)
}
@Test // No exception should be thrown
public void dontCrashIfNotZero()
throws Exception {
object.crashIfZero(1);
}
Exception conditions
class KakaSpec extends Specification {
static final SUBSCRIPTION_ID = 27
private AuxiliarObject auxiliar
void setup() {
(...)
}
def "xxx"() {
(...)
}
}
public class XxxxxxxxxxTest {
private static final long SUBSCRIPTION_ID = 27;
private AuxiliarObject auxiliar;
@Before
public void setUp() throws Exception {
(...)
}
@Test
public void thatXxx() throws Exception {
(...)
}
}
The test class
Groovy syntax sugar
when:
def result = object.getRecords()
then:
result?.list == [1, 47, 23]
Optional types
Collection literals
No ; needed
== operator for equals
Safe navigation ?. operator
Improved error output
Condition not satisfied:
result.value == expectedResult
| | | |
| | | 12
| | false
| 10
PaymentResult(id=2,value=10)
02 MOCKING
Mocks Creation
@Mock
private CustomerDataRegistry customerRegistry;
(...)
private PaymentsCoordinator paymentCoordinator;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
paymentCoordinator = new PaymentsCoordinator(
customerRegistry, (...));
}
CustomerDataRegistry customerRegistry = Mock()
(...)
@Subject PaymentsCoordinator paymentCoordinator =
new PaymentsCoordinator(customerRegistry, (...))
Mocks Creation
@Mock
private CustomerDataRegistry customerRegistry;
(...)
private PaymentsCoordinator paymentCoordinator;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
paymentCoordinator = new PaymentsCoordinator(
customerRegistry, (...));
}
CustomerDataRegistry customerRegistry = Mock()
(...)
@Subject PaymentsCoordinator paymentCoordinator =
new PaymentsCoordinator(customerRegistry, (...))
Mocks Creation
@Mock
private CustomerDataRegistry customerRegistry;
(...)
private PaymentsCoordinator paymentCoordinator;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
paymentCoordinator = new PaymentsCoordinator(
customerRegistry, (...));
}
CustomerDataRegistry customerRegistry = Mock()
(...)
@Subject PaymentsCoordinator paymentCoordinator =
new PaymentsCoordinator(customerRegistry, (...))
Responses declaration
@Test
public void testSuccessfulPaymentWithNewCreditCard() throws Exception {
when(customerRegistry.getCustomerData(SUBSCRIPTION_ID)).thenReturn(CUSTOMER_DATA);
(...)
PaymentResult result = paymentCoordinator.doPayment(paymentData);
(...)
}
def "Succesful payment with new credit card"() {
given: "A registered customer"
customerRegistry.getCustomerData(SUBSCRIPTION_ID) >> CUSTOMER_DATA
(...)
when: "A payment is requested"
def result = paymentCoordinator.doPayment(paymentData)
(...)
}
Interaction expectations
@Test
public void testSuccessfulPaymentWithNewCreditCard() throws Exception {
(...)
when(paymentInteractor.performPaymentInProvider(inputFields))
.thenReturn(PAYMENT_SUCCESSFUL_OUTPUT);
PaymentResult result = paymentCoordinator.doPayment(paymentData);
verify(paymentInteractor).performPaymentInProvider(inputFields)).
(...)
}
def "Succesful payment with new credit card"() {
(...)
when: "A payment is requested"
def result = paymentCoordinator.doPayment(paymentData)
then: "It is sent to the payment provider with successful result"
1 * paymentInteractor.performPaymentInProvider(inputFields) >>
PAYMENT_SUCCESSFUL_OUTPUT
(...)
}
Mocks and Stubs
then:
0 * _
Semantic
Lenient: default
values
Only return values
Stubs: empty objects
Stubs: no interaction
expectations
Mocks: nulls
Call Matching
Arguments matching
mock.method("hello")
mock.method(!"hello")
mock.method()
mock.method(_)
mock.method(*_)
mock.method(_ as String)
mock.method({ l -> l.size() > 3 })
Method matching
customerRegistry._
_
customerRegistry./searchBy.*/(…)
Interactions expectations matching
Parametes matching
Order
1 * subscriber.receive("hello")
0 * subscriber.receive("hello")
(1..3) * subscriber.receive("hello")
(1.._) * subscriber.receive("hello")
(_..3) * subscriber.receive("hello")
_ * subscriber.receive("hello")
then:
1 * mock1.method1(...)
and:
1 * mock2.method2(...)
then:
1 * mock3.calledAfter1And2()
Cardinality * matching constraints
Forget the matchers
@Test
public void testSuccessfulPaymentWithNewCreditCard() throws Exception {
(...)
ArgumentMatcher<Payment> paymentMatcher = new ArgumentMatcher<Payment>() {
public boolean matches(Object payment) {
return ((Payment) payment).getPaymentType() != null;
}
};
verify(paymentInteractor, never()).performPaymentInProvider(
paymentMatcher, anyObject(), eq(VALIDATE));
(...)
}
def "Succesful payment with new credit card"() {
(...)
then:
0 * paymentInteractor.performPaymentInProvider(
{ payment -> payment.storedCard != null }, _, VALIDATE)
(...)
}
03 SOME
PRACTICAL USES
Property-based testing (JUnit parameterized tests)
@RunWith(Parameterized.class)
public class PaymentProcessingOfANewCompletedResponseAuthorizationHandlerTest {
public static Object[][] data() {
return new Object[][] {
{ TransactionStatus.INITIALIZED, TransactionStatus.AUTHORIZED },
{ TransactionStatus.INITIALIZED, TransactionStatus.REFUSED },
{ TransactionStatus.ACCEPTED, TransactionStatus.AUTHORIZED },
{ TransactionStatus.ACCEPTED, TransactionStatus.REFUSED }
};
}
@Parameter public TransactionStatus currentStatus;
@Parameter(value=1) public TransactionStatus responseStatus;
@Test
public void test() throws Exception {
(...)
}
}
Property-based testing (JUnit parameterized tests)
@RunWith(Parameterized.class)
public class PaymentProcessingOfANewCompletedResponseAuthorizationHandlerTest {
public static Object[][] data() {
return new Object[][] {
{ TransactionStatus.INITIALIZED, TransactionStatus.AUTHORIZED },
{ TransactionStatus.INITIALIZED, TransactionStatus.REFUSED },
{ TransactionStatus.ACCEPTED, TransactionStatus.AUTHORIZED },
{ TransactionStatus.ACCEPTED, TransactionStatus.REFUSED }
};
}
@Parameter public TransactionStatus currentStatus;
@Parameter(value=1) public TransactionStatus responseStatus;
@Test
public void test() throws Exception {
(...)
}
}
Property-based testing (JUnit parameterized tests)
@RunWith(Parameterized.class)
public class PaymentProcessingOfANewCompletedResponseAuthorizationHandlerTest {
public static Object[][] data() {
return new Object[][] {
{ TransactionStatus.INITIALIZED, TransactionStatus.AUTHORIZED },
{ TransactionStatus.INITIALIZED, TransactionStatus.REFUSED },
{ TransactionStatus.ACCEPTED, TransactionStatus.AUTHORIZED },
{ TransactionStatus.ACCEPTED, TransactionStatus.REFUSED }
};
}
@Parameter public TransactionStatus currentStatus;
@Parameter(value=1) public TransactionStatus responseStatus;
@Test
public void test() throws Exception {
(...)
}
}
Property-based testing (JUnit parameterized tests)
@RunWith(Parameterized.class)
public class PaymentProcessingOfANewCompletedResponseAuthorizationHandlerTest {
public static Object[][] data() {
return new Object[][] {
{ TransactionStatus.INITIALIZED, TransactionStatus.AUTHORIZED },
{ TransactionStatus.INITIALIZED, TransactionStatus.REFUSED },
{ TransactionStatus.ACCEPTED, TransactionStatus.AUTHORIZED },
{ TransactionStatus.ACCEPTED, TransactionStatus.REFUSED }
};
}
@Parameter public TransactionStatus currentStatus;
@Parameter(value=1) public TransactionStatus responseStatus;
@Test
public void test() throws Exception {
(...)
}
}
Property-based testing (Spock where)
@Unroll
def "Processing of a new completed response from status #currentStatus to #responseStatus"(
currentStatus, responseStatus) {
(...)
when:
def resultTransaction = authorizationHandler.processResponse(statusInfo, FROM_RETRY)
then:
(...)
where:
currentStatus | responseStatus
TransactionStatus.INITIALIZED | TransactionStatus.AUTHORIZED
TransactionStatus.INITIALIZED | TransactionStatus.REFUSED
TransactionStatus.ACCEPTED | TransactionStatus.AUTHORIZED
TransactionStatus.ACCEPTED | TransactionStatus.REFUSED
}
Property-based testing (Spock where)
@Unroll
def "Processing of a new completed response from status #currentStatus to #responseStatus"(
currentStatus, responseStatus) {
(...)
when:
def resultTransaction = authorizationHandler.processResponse(statusInfo, FROM_RETRY)
then:
(...)
where:
currentStatus | responseStatus
TransactionStatus.INITIALIZED | TransactionStatus.AUTHORIZED
TransactionStatus.INITIALIZED | TransactionStatus.REFUSED
TransactionStatus.ACCEPTED | TransactionStatus.AUTHORIZED
TransactionStatus.ACCEPTED | TransactionStatus.REFUSED
}
Config tests (multiline and String interpolation)
def "getRejectionReasonForError with invalid reason mapped"() {
given:
def yaml = """
paymentForm:
rejectionReasonMapping:
${ERROR_CODE}: '${REJECTION_REASON_INVALID}'
"""
def config = formConfig(yaml)
when:
def returnedReason = config.getRejectionReasonForError(ERROR_CODE)
then: "Returns general error"
returnedReason == PaymentRejectionReason.GENERAL_ERROR
}
Config tests (multiline and String interpolation)
def "getRejectionReasonForError with invalid reason mapped"() {
given:
def yaml = """
paymentForm:
rejectionReasonMapping:
${ERROR_CODE}: '${REJECTION_REASON_INVALID}'
"""
def config = formConfig(yaml)
when:
def returnedReason = config.getRejectionReasonForError(ERROR_CODE)
then: "Returns general error"
returnedReason == PaymentRejectionReason.GENERAL_ERROR
}
Builders?
def payment = new PaymentTransaction(
id: TRANSACTION_ID,
subscriptionId: SUBSCRIPTION_ID,
amount: AMOUNT,
lastChangeTimestamp: OLD_TIMESTAMP)
PaymentTransaction payment = new PaymentTransactionBuilder()
.id(TRANSACTION_ID)
.subscriptionId(SUBSCRIPTION_ID)
.amount(AMOUNT)
.lastChangeTimestamp(OLD_TIMESTAMP)
.build();
Default constructor with
named parameters
Builders?
def payment = new PaymentTransaction(
id: TRANSACTION_ID,
subscriptionId: SUBSCRIPTION_ID,
amount: AMOUNT,
lastChangeTimestamp: OLD_TIMESTAMP)
PaymentTransaction payment = new PaymentTransactionBuilder()
.id(TRANSACTION_ID)
.subscriptionId(SUBSCRIPTION_ID)
.amount(AMOUNT)
.lastChangeTimestamp(OLD_TIMESTAMP)
.build();
Default constructor with
named parameters
def "Process an authorized payment "() {
given: "An already authorized payment"
def authorizedPayment = paymentTransactionWith(status: TransactionStatus.AUTHORIZED)
(...)
}
private PaymentTransaction paymentTransactionWith(Map overrides) {
def attrs = [
id: TRANSACTION_ID,
subscriptionId: SUBSCRIPTION_ID,
amount: AMOUNT,
status: TransactionStatus.INITIALIZED,
lastChangeTimestamp: OLD_TIMESTAMP
] + overrides
return new PaymentTransaction(attrs)
}
Objects with default properties
def "Process an authorized payment "() {
given: "An already authorized payment"
def authorizedPayment = paymentTransactionWith(status: TransactionStatus.AUTHORIZED)
(...)
}
private PaymentTransaction paymentTransactionWith(Map overrides) {
def attrs = [
id: TRANSACTION_ID,
subscriptionId: SUBSCRIPTION_ID,
amount: AMOUNT,
status: TransactionStatus.INITIALIZED,
lastChangeTimestamp: OLD_TIMESTAMP
] + overrides
return new PaymentTransaction(attrs)
}
Objects with default properties
Private methods for readability?
PaymentTransaction transaction = givenAnAuthorizedTransaction();
paymentProcessor.processPayment(transaction, RESPONSE_DATA);
verifyTheTransactionIsTransitionedTo(ADDING_BALANCE);
verifyTheTopupIsProcessedCorrectly();
Private methods for readability?
given: "An already authorized transaction"
def transaction = transactionWith(status: AUTHORIZED)
transactionRegistry.getTransactionInfo(TRANSACTION_ID) >> transaction
when: "The payment is processed"
paymentProcessor.processPayment(transaction, RESPONSE_DATA)
then: "The transaction is transitioned to adding balance status"
1 * transactionRegistry.changeTransactionStatus(
TRANSACTION_ID, TransactionStatus.ADDING_BALANCE) >>
transactionWith(status: ADDING_BALANCE)
then: "The topup is processed"
1 * topupInteractor.topup(transaction, RESPONSE_DATA) >>
new PaymentStatusInfo(TRANSACTION_ID, TransactionStatus.BALANCE_ADDED)
PaymentTransaction transaction = givenAnAuthorizedTransaction();
paymentProcessor.processPayment(transaction, RESPONSE_DATA);
verifyTheTransactionIsTransitionedTo(ADDING_BALANCE);
verifyTheTopupIsProcessedCorrectly();
static final MERCADOPAGO_RESULT = [
status: 'approved',
status_detail: 'ok',
description: 'Tuenti (test)',
id: 999999,
authorization_code: 858,
collector_id: 5678,
statement_descriptor: 'WWW.MERCADOPAGO.COM',
card: [
last_four_digits: 1234,
expiration_year: 2016,
expiration_month: 12
],
payer: [
first_name: NAME,
last_name: LAST_NAME,
email: EMAIL,
]
]
Tests involving 3rd party APIs – Thank God for Collection
literals
04 So
what...?
●
Increase abstraction level
Not “programming tests” ® specify test cases
Easy + powerful
Expressivity ® test is also documentation
● Easy to run in continuous integration systems / IDEs
● Better error detection info
Advantages?
● Code Refactors not so safe
● Mocks can only be created in the Spec class
Integration tests with dependency injection overrides ... more
difficult, but possible!
Disadvantages?
● Code Refactors not so safe
● Mocks can only be created in the Spec class
Integration tests with dependency injection overrides ... more
difficult, but possible!
Disadvantages?
class BaseIntegrationSpecification extends TIntegrationSpecification {
@InjectOverride MercadopagoClient mercadopago = Mock()
@Inject PaymentNotificationsService paymentNotificationsServiceMock
(...)
@TIntegrationTestsModule
static class MockedBoundariesModule extends SpockMocksModule {
(...)
}
}
● Code Refactors not so safe
● Mocks can only be created in the Spec class
Integration tests with dependency injection overrides ... more
difficult, but possible!
Disadvantages?
class BaseIntegrationSpecification extends TIntegrationSpecification {
@InjectOverride MercadopagoClient mercadopago = Mock()
@Inject PaymentNotificationsService paymentNotificationsServiceMock
(...)
@TIntegrationTestsModule
static class MockedBoundariesModule extends SpockMocksModule {
(...)
}
}
Do you dare to change?
Do you dare to change?
Andrés ViedmaAndrés Viedma
@andres_viedma@andres_viedma
Questions?

More Related Content

PDF
Property-based testing
PDF
C++ TUTORIAL 3
PPTX
C sharp 8
PDF
C++ TUTORIAL 8
PDF
C++ TUTORIAL 9
PPTX
New presentation oop
PDF
C++ TUTORIAL 1
PDF
C++ TUTORIAL 2
Property-based testing
C++ TUTORIAL 3
C sharp 8
C++ TUTORIAL 8
C++ TUTORIAL 9
New presentation oop
C++ TUTORIAL 1
C++ TUTORIAL 2

What's hot (20)

PPTX
Exceptional exceptions
DOC
Pads lab manual final
DOC
Program for hamming code using c
DOC
Oops lab manual2
PDF
C++ TUTORIAL 10
PDF
C++ TUTORIAL 6
PDF
54602399 c-examples-51-to-108-programe-ee01083101
DOCX
Travel management
PPT
PDF
Promise is a Promise
DOCX
C++ file
PDF
C programs
PDF
Alexey Tsoy Meta Programming in C++ 16.11.17
PPTX
(Rx).NET' way of async programming (.NET summit 2017 Belarus)
DOC
CBSE Class XII Comp sc practical file
DOCX
Ejercicios de programacion
PDF
Обзор фреймворка Twisted
PDF
Static and const members
PDF
C++ TUTORIAL 5
PDF
Pointers
Exceptional exceptions
Pads lab manual final
Program for hamming code using c
Oops lab manual2
C++ TUTORIAL 10
C++ TUTORIAL 6
54602399 c-examples-51-to-108-programe-ee01083101
Travel management
Promise is a Promise
C++ file
C programs
Alexey Tsoy Meta Programming in C++ 16.11.17
(Rx).NET' way of async programming (.NET summit 2017 Belarus)
CBSE Class XII Comp sc practical file
Ejercicios de programacion
Обзор фреймворка Twisted
Static and const members
C++ TUTORIAL 5
Pointers
Ad

Similar to Por qué usar Spock en lugar de JUnit / Mockito para tus tests Java - Codemotion 2016 (20)

PDF
Write a Java program in which a method named digitSum that accepts a.pdf
PPTX
Самые вкусные баги из игрового кода: как ошибаются наши коллеги-программисты ...
PDF
ES3-2020-07 Testing techniques
PPTX
Computational Physics Cpp Portiiion.pptx
DOC
Adodb Scripts And Some Sample Scripts[1]
DOC
Adodb Scripts And Some Sample Scripts[1]
PPTX
Computational PhysicsssComputational Physics.pptx
PDF
Developer Testing Tools Roundup
PDF
Improving the java type system
PPTX
A Test of Strength
PDF
C++ code only(Retrieve of Malik D., 2015, p. 742) Programming Exer.pdf
PDF
Classic Games Development with Drools
PDF
Automatically Repairing Test Cases for Evolving Method Declarations
PPTX
Best Bugs from Games: Fellow Programmers' Mistakes
PDF
Rechecking SharpDevelop: Any New Bugs?
PDF
Unbearable Test Code Smell
PDF
Bdd for-dso-1227123516572504-8
PDF
Test-driven Development (TDD)
DOCX
Rhino Mocks
PDF
C programming
Write a Java program in which a method named digitSum that accepts a.pdf
Самые вкусные баги из игрового кода: как ошибаются наши коллеги-программисты ...
ES3-2020-07 Testing techniques
Computational Physics Cpp Portiiion.pptx
Adodb Scripts And Some Sample Scripts[1]
Adodb Scripts And Some Sample Scripts[1]
Computational PhysicsssComputational Physics.pptx
Developer Testing Tools Roundup
Improving the java type system
A Test of Strength
C++ code only(Retrieve of Malik D., 2015, p. 742) Programming Exer.pdf
Classic Games Development with Drools
Automatically Repairing Test Cases for Evolving Method Declarations
Best Bugs from Games: Fellow Programmers' Mistakes
Rechecking SharpDevelop: Any New Bugs?
Unbearable Test Code Smell
Bdd for-dso-1227123516572504-8
Test-driven Development (TDD)
Rhino Mocks
C programming
Ad

Recently uploaded (20)

PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 41
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PPTX
Transform Your Business with a Software ERP System
PDF
System and Network Administration Chapter 2
PDF
System and Network Administraation Chapter 3
PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PDF
EN-Survey-Report-SAP-LeanIX-EA-Insights-2025.pdf
PPTX
L1 - Introduction to python Backend.pptx
PDF
Adobe Illustrator 28.6 Crack My Vision of Vector Design
PPTX
VVF-Customer-Presentation2025-Ver1.9.pptx
PDF
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PPTX
Introduction to Artificial Intelligence
PPTX
Reimagine Home Health with the Power of Agentic AI​
PTS Company Brochure 2025 (1).pdf.......
Internet Downloader Manager (IDM) Crack 6.42 Build 41
How to Migrate SBCGlobal Email to Yahoo Easily
Transform Your Business with a Software ERP System
System and Network Administration Chapter 2
System and Network Administraation Chapter 3
wealthsignaloriginal-com-DS-text-... (1).pdf
EN-Survey-Report-SAP-LeanIX-EA-Insights-2025.pdf
L1 - Introduction to python Backend.pptx
Adobe Illustrator 28.6 Crack My Vision of Vector Design
VVF-Customer-Presentation2025-Ver1.9.pptx
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
Which alternative to Crystal Reports is best for small or large businesses.pdf
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
Navsoft: AI-Powered Business Solutions & Custom Software Development
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
Upgrade and Innovation Strategies for SAP ERP Customers
Introduction to Artificial Intelligence
Reimagine Home Health with the Power of Agentic AI​

Por qué usar Spock en lugar de JUnit / Mockito para tus tests Java - Codemotion 2016

  • 1. MADRID · NOV 18-19 · 2016 Why Using by @andres_viedma instead of in your Java Tests
  • 4. A testing framework Tests are written in 100% compatible with Java code Runs on http://spockframework.org/
  • 5. A basic JUnit test @Test public void thatAddAPositiveNumberToZeroReturnsTheSamePositiveNumber() throws Exception { int positiveNumber = 23; int zero = 0; int result = zero + positiveNumber; assertEquals(positiveNumber, result); }
  • 6. A basic Spock test def "Add a positive number to zero returns the same positive number"() { given: def positiveNumber = 23 def zero = 0 when: def result = zero + positiveNumber then: result == positiveNumber }
  • 7. A basic Spock test def "Add a positive number to zero returns the same positive number"() { given: def positiveNumber = 23 def zero = 0 when: def result = zero + positiveNumber then: result == positiveNumber }
  • 8. A basic Spock test DSL def "Add a positive number to zero returns the same positive number"() { given: def positiveNumber = 23 def zero = 0 when: def result = zero + positiveNumber then: result == positiveNumber }
  • 9. String literals def "Add a positive number to zero returns the same positive number"() { (...) } @Test public void thatAddAPositiveNumberToZeroReturnsTheSamePositiveNumber() throws Exception { (...) }
  • 10. String literals def "Add a positive number to zero returns the same positive number"() { (...) } @Test public void thatAddAPositiveNumberToZeroReturnsTheSamePositiveNumber() throws Exception { (...) } def "En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua, rocín flaco y galgo corredor. "() { (...) }
  • 11. Spock Blocks given: def positiveNumber = 23 def zero = 0 when: def result = zero + positiveNumber then: result == positiveNumber int positiveNumber = 23; int zero = 0; int result = zero + positiveNumber; assertEquals(positiveNumber, result); Programming vs. Specification
  • 12. Spock Blocks given: def positiveNumber = 23 def zero = 0 when: def result = zero + positiveNumber then: result == positiveNumber // Given int positiveNumber = 23; int zero = 0; // When int result = zero + positiveNumber; // Then assertEquals(positiveNumber, result); Compilation check Programming vs. Specification
  • 13. Documented Spock Blocks given: "A positive number" def positiveNumber = 23 and: "Well... a SPECTACULAR zero" def zero = 0 when: "The number is added to zero" def result = zero + positiveNumber then: "The result is the same number" result == positiveNumber int positiveNumber = 23; int zero = 0; int result = zero + positiveNumber; assertEquals(positiveNumber, result); Documents purpose Helps thinking
  • 14. expect block given: "A positive number" def positiveNumber = 23 expect: "That added to zero results the same number" zero + positiveNumber == positiveNumber Replaces when-then for simple functional tests
  • 15. Expectations (then / expect) given: def positiveNumber = 23 def zero = 0 when: def result = zero + positiveNumber then: result == positiveNumber int positiveNumber = 23; int zero = 0; int result = zero + positiveNumber; assertEquals(positiveNumber, result);
  • 16. Multiple expectations then: result == positiveNumber !collection.isEmpty() then: result == positiveNumber and: !collection.isEmpty()
  • 17. Multiple expectations then: result == positiveNumber !collection.isEmpty() then: def expectedResult = positiveNumber result == expectedResult Only conditions and variable assignments allowed then: result == positiveNumber and: !collection.isEmpty()
  • 18. def "Crash if zero"() { when: object.crashIfZero(0) then: thrown(ZeroException) } @Test(expected = OperationException.class) public void crashIfZero() throws Exception { object.crashIfZero(0); } Exception conditions
  • 19. def "Crash if zero"() { when: object.crashIfZero(0) then: thrown(ZeroException) } @Test(expected = OperationException.class) public void crashIfZero() throws Exception { object.crashIfZero(0); } def "Dont crash if not zero"() { when: object.crashIfZero(1) then: notThrown(ZeroException) } @Test // No exception should be thrown public void dontCrashIfNotZero() throws Exception { object.crashIfZero(1); } Exception conditions
  • 20. class KakaSpec extends Specification { static final SUBSCRIPTION_ID = 27 private AuxiliarObject auxiliar void setup() { (...) } def "xxx"() { (...) } } public class XxxxxxxxxxTest { private static final long SUBSCRIPTION_ID = 27; private AuxiliarObject auxiliar; @Before public void setUp() throws Exception { (...) } @Test public void thatXxx() throws Exception { (...) } } The test class
  • 21. Groovy syntax sugar when: def result = object.getRecords() then: result?.list == [1, 47, 23] Optional types Collection literals No ; needed == operator for equals Safe navigation ?. operator
  • 22. Improved error output Condition not satisfied: result.value == expectedResult | | | | | | | 12 | | false | 10 PaymentResult(id=2,value=10)
  • 24. Mocks Creation @Mock private CustomerDataRegistry customerRegistry; (...) private PaymentsCoordinator paymentCoordinator; @Before public void setup() { MockitoAnnotations.initMocks(this); paymentCoordinator = new PaymentsCoordinator( customerRegistry, (...)); } CustomerDataRegistry customerRegistry = Mock() (...) @Subject PaymentsCoordinator paymentCoordinator = new PaymentsCoordinator(customerRegistry, (...))
  • 25. Mocks Creation @Mock private CustomerDataRegistry customerRegistry; (...) private PaymentsCoordinator paymentCoordinator; @Before public void setup() { MockitoAnnotations.initMocks(this); paymentCoordinator = new PaymentsCoordinator( customerRegistry, (...)); } CustomerDataRegistry customerRegistry = Mock() (...) @Subject PaymentsCoordinator paymentCoordinator = new PaymentsCoordinator(customerRegistry, (...))
  • 26. Mocks Creation @Mock private CustomerDataRegistry customerRegistry; (...) private PaymentsCoordinator paymentCoordinator; @Before public void setup() { MockitoAnnotations.initMocks(this); paymentCoordinator = new PaymentsCoordinator( customerRegistry, (...)); } CustomerDataRegistry customerRegistry = Mock() (...) @Subject PaymentsCoordinator paymentCoordinator = new PaymentsCoordinator(customerRegistry, (...))
  • 27. Responses declaration @Test public void testSuccessfulPaymentWithNewCreditCard() throws Exception { when(customerRegistry.getCustomerData(SUBSCRIPTION_ID)).thenReturn(CUSTOMER_DATA); (...) PaymentResult result = paymentCoordinator.doPayment(paymentData); (...) } def "Succesful payment with new credit card"() { given: "A registered customer" customerRegistry.getCustomerData(SUBSCRIPTION_ID) >> CUSTOMER_DATA (...) when: "A payment is requested" def result = paymentCoordinator.doPayment(paymentData) (...) }
  • 28. Interaction expectations @Test public void testSuccessfulPaymentWithNewCreditCard() throws Exception { (...) when(paymentInteractor.performPaymentInProvider(inputFields)) .thenReturn(PAYMENT_SUCCESSFUL_OUTPUT); PaymentResult result = paymentCoordinator.doPayment(paymentData); verify(paymentInteractor).performPaymentInProvider(inputFields)). (...) } def "Succesful payment with new credit card"() { (...) when: "A payment is requested" def result = paymentCoordinator.doPayment(paymentData) then: "It is sent to the payment provider with successful result" 1 * paymentInteractor.performPaymentInProvider(inputFields) >> PAYMENT_SUCCESSFUL_OUTPUT (...) }
  • 29. Mocks and Stubs then: 0 * _ Semantic Lenient: default values Only return values Stubs: empty objects Stubs: no interaction expectations Mocks: nulls
  • 30. Call Matching Arguments matching mock.method("hello") mock.method(!"hello") mock.method() mock.method(_) mock.method(*_) mock.method(_ as String) mock.method({ l -> l.size() > 3 }) Method matching customerRegistry._ _ customerRegistry./searchBy.*/(…)
  • 31. Interactions expectations matching Parametes matching Order 1 * subscriber.receive("hello") 0 * subscriber.receive("hello") (1..3) * subscriber.receive("hello") (1.._) * subscriber.receive("hello") (_..3) * subscriber.receive("hello") _ * subscriber.receive("hello") then: 1 * mock1.method1(...) and: 1 * mock2.method2(...) then: 1 * mock3.calledAfter1And2() Cardinality * matching constraints
  • 32. Forget the matchers @Test public void testSuccessfulPaymentWithNewCreditCard() throws Exception { (...) ArgumentMatcher<Payment> paymentMatcher = new ArgumentMatcher<Payment>() { public boolean matches(Object payment) { return ((Payment) payment).getPaymentType() != null; } }; verify(paymentInteractor, never()).performPaymentInProvider( paymentMatcher, anyObject(), eq(VALIDATE)); (...) } def "Succesful payment with new credit card"() { (...) then: 0 * paymentInteractor.performPaymentInProvider( { payment -> payment.storedCard != null }, _, VALIDATE) (...) }
  • 34. Property-based testing (JUnit parameterized tests) @RunWith(Parameterized.class) public class PaymentProcessingOfANewCompletedResponseAuthorizationHandlerTest { public static Object[][] data() { return new Object[][] { { TransactionStatus.INITIALIZED, TransactionStatus.AUTHORIZED }, { TransactionStatus.INITIALIZED, TransactionStatus.REFUSED }, { TransactionStatus.ACCEPTED, TransactionStatus.AUTHORIZED }, { TransactionStatus.ACCEPTED, TransactionStatus.REFUSED } }; } @Parameter public TransactionStatus currentStatus; @Parameter(value=1) public TransactionStatus responseStatus; @Test public void test() throws Exception { (...) } }
  • 35. Property-based testing (JUnit parameterized tests) @RunWith(Parameterized.class) public class PaymentProcessingOfANewCompletedResponseAuthorizationHandlerTest { public static Object[][] data() { return new Object[][] { { TransactionStatus.INITIALIZED, TransactionStatus.AUTHORIZED }, { TransactionStatus.INITIALIZED, TransactionStatus.REFUSED }, { TransactionStatus.ACCEPTED, TransactionStatus.AUTHORIZED }, { TransactionStatus.ACCEPTED, TransactionStatus.REFUSED } }; } @Parameter public TransactionStatus currentStatus; @Parameter(value=1) public TransactionStatus responseStatus; @Test public void test() throws Exception { (...) } }
  • 36. Property-based testing (JUnit parameterized tests) @RunWith(Parameterized.class) public class PaymentProcessingOfANewCompletedResponseAuthorizationHandlerTest { public static Object[][] data() { return new Object[][] { { TransactionStatus.INITIALIZED, TransactionStatus.AUTHORIZED }, { TransactionStatus.INITIALIZED, TransactionStatus.REFUSED }, { TransactionStatus.ACCEPTED, TransactionStatus.AUTHORIZED }, { TransactionStatus.ACCEPTED, TransactionStatus.REFUSED } }; } @Parameter public TransactionStatus currentStatus; @Parameter(value=1) public TransactionStatus responseStatus; @Test public void test() throws Exception { (...) } }
  • 37. Property-based testing (JUnit parameterized tests) @RunWith(Parameterized.class) public class PaymentProcessingOfANewCompletedResponseAuthorizationHandlerTest { public static Object[][] data() { return new Object[][] { { TransactionStatus.INITIALIZED, TransactionStatus.AUTHORIZED }, { TransactionStatus.INITIALIZED, TransactionStatus.REFUSED }, { TransactionStatus.ACCEPTED, TransactionStatus.AUTHORIZED }, { TransactionStatus.ACCEPTED, TransactionStatus.REFUSED } }; } @Parameter public TransactionStatus currentStatus; @Parameter(value=1) public TransactionStatus responseStatus; @Test public void test() throws Exception { (...) } }
  • 38. Property-based testing (Spock where) @Unroll def "Processing of a new completed response from status #currentStatus to #responseStatus"( currentStatus, responseStatus) { (...) when: def resultTransaction = authorizationHandler.processResponse(statusInfo, FROM_RETRY) then: (...) where: currentStatus | responseStatus TransactionStatus.INITIALIZED | TransactionStatus.AUTHORIZED TransactionStatus.INITIALIZED | TransactionStatus.REFUSED TransactionStatus.ACCEPTED | TransactionStatus.AUTHORIZED TransactionStatus.ACCEPTED | TransactionStatus.REFUSED }
  • 39. Property-based testing (Spock where) @Unroll def "Processing of a new completed response from status #currentStatus to #responseStatus"( currentStatus, responseStatus) { (...) when: def resultTransaction = authorizationHandler.processResponse(statusInfo, FROM_RETRY) then: (...) where: currentStatus | responseStatus TransactionStatus.INITIALIZED | TransactionStatus.AUTHORIZED TransactionStatus.INITIALIZED | TransactionStatus.REFUSED TransactionStatus.ACCEPTED | TransactionStatus.AUTHORIZED TransactionStatus.ACCEPTED | TransactionStatus.REFUSED }
  • 40. Config tests (multiline and String interpolation) def "getRejectionReasonForError with invalid reason mapped"() { given: def yaml = """ paymentForm: rejectionReasonMapping: ${ERROR_CODE}: '${REJECTION_REASON_INVALID}' """ def config = formConfig(yaml) when: def returnedReason = config.getRejectionReasonForError(ERROR_CODE) then: "Returns general error" returnedReason == PaymentRejectionReason.GENERAL_ERROR }
  • 41. Config tests (multiline and String interpolation) def "getRejectionReasonForError with invalid reason mapped"() { given: def yaml = """ paymentForm: rejectionReasonMapping: ${ERROR_CODE}: '${REJECTION_REASON_INVALID}' """ def config = formConfig(yaml) when: def returnedReason = config.getRejectionReasonForError(ERROR_CODE) then: "Returns general error" returnedReason == PaymentRejectionReason.GENERAL_ERROR }
  • 42. Builders? def payment = new PaymentTransaction( id: TRANSACTION_ID, subscriptionId: SUBSCRIPTION_ID, amount: AMOUNT, lastChangeTimestamp: OLD_TIMESTAMP) PaymentTransaction payment = new PaymentTransactionBuilder() .id(TRANSACTION_ID) .subscriptionId(SUBSCRIPTION_ID) .amount(AMOUNT) .lastChangeTimestamp(OLD_TIMESTAMP) .build(); Default constructor with named parameters
  • 43. Builders? def payment = new PaymentTransaction( id: TRANSACTION_ID, subscriptionId: SUBSCRIPTION_ID, amount: AMOUNT, lastChangeTimestamp: OLD_TIMESTAMP) PaymentTransaction payment = new PaymentTransactionBuilder() .id(TRANSACTION_ID) .subscriptionId(SUBSCRIPTION_ID) .amount(AMOUNT) .lastChangeTimestamp(OLD_TIMESTAMP) .build(); Default constructor with named parameters
  • 44. def "Process an authorized payment "() { given: "An already authorized payment" def authorizedPayment = paymentTransactionWith(status: TransactionStatus.AUTHORIZED) (...) } private PaymentTransaction paymentTransactionWith(Map overrides) { def attrs = [ id: TRANSACTION_ID, subscriptionId: SUBSCRIPTION_ID, amount: AMOUNT, status: TransactionStatus.INITIALIZED, lastChangeTimestamp: OLD_TIMESTAMP ] + overrides return new PaymentTransaction(attrs) } Objects with default properties
  • 45. def "Process an authorized payment "() { given: "An already authorized payment" def authorizedPayment = paymentTransactionWith(status: TransactionStatus.AUTHORIZED) (...) } private PaymentTransaction paymentTransactionWith(Map overrides) { def attrs = [ id: TRANSACTION_ID, subscriptionId: SUBSCRIPTION_ID, amount: AMOUNT, status: TransactionStatus.INITIALIZED, lastChangeTimestamp: OLD_TIMESTAMP ] + overrides return new PaymentTransaction(attrs) } Objects with default properties
  • 46. Private methods for readability? PaymentTransaction transaction = givenAnAuthorizedTransaction(); paymentProcessor.processPayment(transaction, RESPONSE_DATA); verifyTheTransactionIsTransitionedTo(ADDING_BALANCE); verifyTheTopupIsProcessedCorrectly();
  • 47. Private methods for readability? given: "An already authorized transaction" def transaction = transactionWith(status: AUTHORIZED) transactionRegistry.getTransactionInfo(TRANSACTION_ID) >> transaction when: "The payment is processed" paymentProcessor.processPayment(transaction, RESPONSE_DATA) then: "The transaction is transitioned to adding balance status" 1 * transactionRegistry.changeTransactionStatus( TRANSACTION_ID, TransactionStatus.ADDING_BALANCE) >> transactionWith(status: ADDING_BALANCE) then: "The topup is processed" 1 * topupInteractor.topup(transaction, RESPONSE_DATA) >> new PaymentStatusInfo(TRANSACTION_ID, TransactionStatus.BALANCE_ADDED) PaymentTransaction transaction = givenAnAuthorizedTransaction(); paymentProcessor.processPayment(transaction, RESPONSE_DATA); verifyTheTransactionIsTransitionedTo(ADDING_BALANCE); verifyTheTopupIsProcessedCorrectly();
  • 48. static final MERCADOPAGO_RESULT = [ status: 'approved', status_detail: 'ok', description: 'Tuenti (test)', id: 999999, authorization_code: 858, collector_id: 5678, statement_descriptor: 'WWW.MERCADOPAGO.COM', card: [ last_four_digits: 1234, expiration_year: 2016, expiration_month: 12 ], payer: [ first_name: NAME, last_name: LAST_NAME, email: EMAIL, ] ] Tests involving 3rd party APIs – Thank God for Collection literals
  • 50. ● Increase abstraction level Not “programming tests” ® specify test cases Easy + powerful Expressivity ® test is also documentation ● Easy to run in continuous integration systems / IDEs ● Better error detection info Advantages?
  • 51. ● Code Refactors not so safe ● Mocks can only be created in the Spec class Integration tests with dependency injection overrides ... more difficult, but possible! Disadvantages?
  • 52. ● Code Refactors not so safe ● Mocks can only be created in the Spec class Integration tests with dependency injection overrides ... more difficult, but possible! Disadvantages? class BaseIntegrationSpecification extends TIntegrationSpecification { @InjectOverride MercadopagoClient mercadopago = Mock() @Inject PaymentNotificationsService paymentNotificationsServiceMock (...) @TIntegrationTestsModule static class MockedBoundariesModule extends SpockMocksModule { (...) } }
  • 53. ● Code Refactors not so safe ● Mocks can only be created in the Spec class Integration tests with dependency injection overrides ... more difficult, but possible! Disadvantages? class BaseIntegrationSpecification extends TIntegrationSpecification { @InjectOverride MercadopagoClient mercadopago = Mock() @Inject PaymentNotificationsService paymentNotificationsServiceMock (...) @TIntegrationTestsModule static class MockedBoundariesModule extends SpockMocksModule { (...) } }
  • 54. Do you dare to change?
  • 55. Do you dare to change?

Editor's Notes

  • #2: Buenas a todos, gracias por venir. Java Testing ==&amp;gt; cuánta gente realmente prueba?
  • #5: - opensource, std. Groovy
  • #53: Fácil pensar – no te da tanto, ¿por qué cambiar? Ejemplo parecido – salto de altura Dick Fosbury – Mexico 68 Hasta años después no se popularizó del todo – quizá lo que pensaban entonces el resto de saltadores es que no te da tanto, que por qué cambiar, que ellos llevaban toooda la vida … saltando así.