Checking Parameters Mock Method Invocation in Spock
Arthur Arts wrote an blog post about Using ArgumentCaptor for generic collections with Mockito. 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 a 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.jdriven.spock
class ClassUnderTest {
private final Greeting greeting
ClassUnderTest(final Greeting greeting) {
this.greeting = greeting
}
String greeting(final List<Person> people) {
greeting.sayHello(people)
}
}
package com.jdriven.spock
interface Greeting {
String sayHello(final List<Person> people)
}
package com.jdriven.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.jdriven.spock
import spock.lang.Specification
class SampleSpecification extends Specification {
def "check sayHello is invoked with people in greeting method"() {
given:
final Greeting greeting = Mock()
final ClassUnderTest classUnderTest = new ClassUnderTest(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, like an ArgumentCaptor, 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 for the argument.
package com.jdriven.spock
import spock.lang.Specification
class SampleSpecification extends Specification {
def "check sayHello is invoked with people in greeting method"() {
given:
final Greeting greeting = Mock()
final ClassUnderTest classUnderTest = new ClassUnderTest(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