My colleague Arthur Arts has written a blog post Tasty Test Tip: Using ArgumentCaptor for generic collections with Mockito. This inspired me to do the same in Spock. With the ArgumentCaptor in Mockito the parameters of a method call to a mock are captured and can be verified with assertions. In Spock we can also get a hold on the arguments that are passed to method call of a mock and we can write assertions to check the parameters for certain conditions.
When we create a mock in Spock and invoke a method on the mock the arguments are matched using the equals()
implementation of the argument type. If they are not equal Spock will tell us by showing a message that there are too few invocations of the method call. Let's show this with an example. First we create some classes we want to test:
package com.mrhaki.spock public class ClassUnderTest { private final Greeting greeting ClassUnderTest(final Greeting greeting) { this.greeting = greeting } String greeting(final List<Person> people) { greeting.sayHello(people) } }
package com.mrhaki.spock interface Greeting { String sayHello(final List<Person> people) }
package com.mrhaki.spock @groovy.transform.Canonical class Person { String name }
Now we can write a Spock specification to test ClassUnderTest
. We will now use the default matching of arguments of a mock provided by Spock.
package com.mrhaki.spock import spock.lang.Specification class SampleSpecification extends Specification { final ClassUnderTest classUnderTest = new ClassUnderTest() def "check sayHello is invoked with people in greeting method"() { given: final Greeting greeting = Mock() classUnderTest.greeting = greeting and: final List<Person> people = ['mrhakis', 'hubert'].collect { new Person(name: it) } when: final String greetingResult = classUnderTest.greeting(people) then: 1 * greeting.sayHello([new Person(name: 'mrhaki'), new Person(name: 'hubert')]) } }
When we execute the specification we get a failure with the message that there are too few invocations:
... Too few invocations for: 1 * greeting.sayHello([new Person(name: 'mrhaki'), new Person(name: 'hubert')]) (0 invocations) Unmatched invocations (ordered by similarity): 1 * greeting.sayHello([com.jdriven.spock.Person(mrhakis), com.jdriven.spock.Person(hubert)]) ...
To capture the arguments we have to use a different syntax for the method invocation on the mock. This time we define the method can be invoked with any number of arguments ((*_)
) and then use a closure to capture the arguments. The arguments are passed to the closure as a list. We can then get the argument we want and write an assert statement.
package com.mrhaki.spock import spock.lang.Specification class SampleSpecification extends Specification { final ClassUnderTest classUnderTest = new ClassUnderTest() def "check sayHello is invoked with people in greeting method"() { given: final Greeting greeting = Mock() classUnderTest.greeting = greeting and: final List<Person> people = ['mrhakis', 'hubert'].collect { new Person(name: it) } when: final String greetingResult = classUnderTest.greeting(people) then: 1 * greeting.sayHello(*_) >> { arguments -> final List<Person> argumentPeople = arguments[0] assert argumentPeople == [new Person(name: 'mrhaki'), new Person(name: 'hubert')] } } }
We run the specification again and it will fail again (of course), but this time we get an assertion message:
... Condition not satisfied: argumentPeople == [new Person(name: 'mrhaki'), new Person(name: 'hubert')] | | | | | | | com.jdriven.spock.Person(hubert) | | com.jdriven.spock.Person(mrhaki) | false [com.jdriven.spock.Person(mrhakis), com.jdriven.spock.Person(hubert)] at com.jdriven.spock.SampleSpecification.check sayHello is invoked with people in greeting method_closure2(SampleSpecification.groovy:25) ...
Code written with Spock 0.7-groovy-2.0