What’s New In Spock 0.4? Episode 2: Better Mocking
As you may know, Spock comes with its own mocking framework, tightly integrated with the rest of the specification language. While certainly useful and already quite powerful, the mocking framework used to be one of the lesser developed parts of Spock, and had a few rough edges. Well, not anymore! Here are the improvements we’ve made for 0.4:
1. All mocks are thread-safe
Previously, things could go wrong if a mock was called from a thread other than the spec runner’s thread. Now, the mocking framework will behave correctly no matter how many threads you throw at it.
2. Vararg syntax
In Groovy, all methods whose last parameter has an array type can be called with vararg syntax (even methods implemented in Java). Therefore, it was only logical for Spock to support the same vararg syntax when defining interactions with such methods. Here is an example.
Suppose you have the following Java interface:
class Subscriber {
public void receive(String... messages); // or: receive(String[] messages)
}
An interaction between a Publisher and its Subscriber could be described as follows:
given:
def publisher = new Publisher() // this is the object under test
def subscriber = Mock(Subscriber)
publisher.add(subscriber)
when:
publisher.sendNotifications() // let's say this should send "foo", "bar", and "baz"
then:
1 * subscriber.receive("foo", "bar", "baz") // vararg syntax
Alternatively, you could describe the interaction as follows:
then: 1 * subscriber.receive(["foo", "bar", "baz"]) // list syntax
Prior to 0.4, only the list syntax would have worked; the vararg syntax would have resulted in an InteractionNotSatisfiedError.
3. Sensible defaults for toString(), equals(), and hashCode()
Previously, a mock object’s toString(), equals(), and hashCode() methods didn’t get any special treatment. If you didn’t say otherwise, they would always return null, false, and 0, respectively. As of 0.4, more sensible defaults are in place: toString() now returns a descriptive message, equals() implements object identity, and hashCode() delegates to System.identityHashCode() (which behaves like Object.hashCode()).
Another change is that toString(), equals(), and hashCode() no longer match the wildcard method (as in “1 * foo._()”). This prevents tests from failing in the presence of tools like debuggers, which often call these methods for their own purposes.
Despite all this, toString(), equals(), and hashCode() can still be stubbed (and even mocked) like any other method, overriding their default behavior. Most Java mocking frameworks don’t support this feature.
4. _ as an abbreviation for _._
By default, Spock allows interactions that haven’t been specified explicitly, and returns default values (null, 0, false) for them. This helps to avoid over-specification and makes tests more resilient to change. In cases where you want to be more strict, add the following as your last interaction:
0 * _._ // no (other) call of any method on any mock object
In 0.4, this can be abbreviated to:
0 * _ // nothing else!
5. Support for property syntax
Properties and getter methods can now be stubbed with property syntax:
item.price >> 42
However, mocking a property setter still requires method syntax:
1 * item.setPrice(42) // not: 1 * (item.price = 42)
6. Ordered interactions
By default, interactions may occur in any order. For example:
when: ... then: 1 * foo.moo() 2 * bar.baz()
Here we are expecting a total of three calls, but don’t demand a particular order. As a consequence, the following invocation order would be acceptable:
bar.baz()
foo.moo()
bar.baz()
In general this is a good thing, because it prevents you from specifying unimportant details which might change over time. However, sometimes order is really important. Therefore, you can now impose ordering constraints by using multiple then-blocks:
when: ... then: 1 * tank.fill() 1 * door.close() then: 1 * plane.takeOff()
To paraphrase: “I don’t care if you first fill the tank or close the cabin door, but you must do both before takeoff!” If this constraint isn’t met, Spock will throw a WrongInvocationOrderError.
That’s it for better mocking in Spock 0.4. In the next part of this series, we’ll have a look at how Spock 0.4 simplifies testing concurrent code.
This totally rocks – I find spock the best testing framework I know so far!
All these improvements are really very exciting! Well done!
Thanks guys. The really exciting stuff is still to come.
I really like that similar to Mockito I only have to specify behaviour for the methods my test actually cares about. Can I use Hamcrest matchers as parameters to the mock method when verifying in the ‘then’ block?
Instead of a Hamcrest matcher, you use a closure-based constraint:
1 * foo.bar({ it > 5 })
The one downside is that you won’t be able to tell from the failure message which value was passed (there’s certainly room for improvement here). To get around that, you can do:
1 * foo.bar(_) >> { assert it[0] > 5 }
This will give you the usual condition output.
I should add that Spock supports other argument constraints besides the closure-based ones. Have a look here: http://code.google.com/p/spock/wiki/Interactions#Argument_constraints