Loading...

May 17, 2013

Spocklight: Writing Assertions for Arguments Mock Methods

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