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