Interaction-based testing is a design and testing technique that emerged in the Extreme Programming (XP) community in the early 2000’s. Focusing on the behavior of objects rather than their state, it explores how the object(s) under specification interact, by way of method calls, with their collaborators.
For example, suppose we have a Publisher
that sends messages to its `Subscriber`s:
class Publisher {
List<Subscriber> subscribers = []
int messageCount = 0
void send(String message){
subscribers*.receive(message)
messageCount++
}
}
interface Subscriber {
void receive(String message)
}
class PublisherSpec extends Specification {
Publisher publisher = new Publisher()
}
How are we going to test Publisher
? With state-based testing, we can verify that the publisher keeps track of its
subscribers. The more interesting question, though, is whether a message sent by the publisher
is received by the subscribers. To answer this question, we need a special implementation of
Subscriber
that listens in on the conversation between the publisher and its subscribers. Such an
implementation is called a mock object.
While we could certainly create a mock implementation of Subscriber
by hand, writing and maintaining this code
can get unpleasant as the number of methods and complexity of interactions increases. This is where mocking frameworks
come in: They provide a way to describe the expected interactions between an object under specification and its
collaborators, and can generate mock implementations of collaborators that verify these expectations.
The Java world has no shortage of popular and mature mocking frameworks: JMock, EasyMock, Mockito, to name just a few. Although each of these tools can be used together with Spock, we decided to roll our own mocking framework, tightly integrated with Spock’s specification language. This decision was driven by the desire to leverage all of Groovy’s capabilities to make interaction-based tests easier to write, more readable, and ultimately more fun. We hope that by the end of this chapter, you will agree that we have achieved these goals.
Except where indicated, all features of Spock’s mocking framework work both for testing Java and Groovy code.
Creating Mock Objects
Mock objects are created with the MockingApi.Mock()
method.[1]
Let’s create two mock subscribers:
def subscriber = Mock(Subscriber)
def subscriber2 = Mock(Subscriber)
Alternatively, the following Java-like syntax is supported, which may give better IDE support:
Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()
Here, the mock’s type is inferred from the variable type on the left-hand side of the assignment.
Note
|
If the mock’s type is given on the left-hand side of the assignment, it’s permissible (though not required) to omit it on the right-hand side. |
Mock objects literally implement (or, in the case of a class, extend) the type they stand in for. In other
words, in our example subscriber
is-a Subscriber
. Hence it can be passed to statically typed (Java)
code that expects this type.
Default Behavior of Mock Objects
Initially, mock objects have no behavior. Calling methods on them is allowed but has no effect other than returning
the default value for the method’s return type (false
, 0
, or null
). An exception are the Object.equals
,
Object.hashCode
, and Object.toString
methods, which have the following default behavior: A mock object is only
equal to itself, has a unique hash code, and a string representation that includes the name of the type it represents.
This default behavior is overridable by stubbing the methods, which we will learn about in the Stubbing section.
Injecting Mock Objects into Code Under Specification
After creating the publisher and its subscribers, we need to make the latter known to the former:
class PublisherSpec extends Specification {
Publisher publisher = new Publisher()
Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()
def setup() {
publisher.subscribers << subscriber // << is a Groovy shorthand for List.add()
publisher.subscribers << subscriber2
}
We are now ready to describe the expected interactions between the two parties.
Mocking
Mocking is the act of describing (mandatory) interactions between the object under specification and its collaborators. Here is an example:
def "should send messages to all subscribers"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("hello")
1 * subscriber2.receive("hello")
}
Read out aloud: "When the publisher sends a 'hello' message, then both subscribers should receive that message exactly once."
When this feature method gets run, all invocations on mock objects that occur while executing the
when
block will be matched against the interactions described in the then:
block. If one of the interactions isn’t
satisfied, a (subclass of) InteractionNotSatisfiedError
will be thrown. This verification happens automatically
and does not require any additional code.
Interactions
Let’s take a closer look at the then:
block. It contains two interactions, each of which has four distinct
parts: a cardinality, a target constraint, a method constraint, and an argument constraint:
1 * subscriber.receive("hello") | | | | | | | argument constraint | | method constraint | target constraint cardinality
Cardinality
The cardinality of an interaction describes how often a method call is expected. It can either be a fixed number or a range:
1 * subscriber.receive("hello") // exactly one call
0 * subscriber.receive("hello") // zero calls
(1..3) * subscriber.receive("hello") // between one and three calls (inclusive)
(1.._) * subscriber.receive("hello") // at least one call
(_..3) * subscriber.receive("hello") // at most three calls
_ * subscriber.receive("hello") // any number of calls, including zero
// (rarely needed; see 'Strict Mocking')
Target Constraint
The target constraint of an interaction describes which mock object is expected to receive the method call:
1 * subscriber.receive("hello") // a call to 'subscriber'
1 * _.receive("hello") // a call to any mock object
Method Constraint
The method constraint of an interaction describes which method is expected to be called:
1 * subscriber.receive("hello") // a method named 'receive'
1 * subscriber./r.*e/("hello") // a method whose name matches the given regular expression
// (here: method name starts with 'r' and ends in 'e')
When expecting a call to a getter method, Groovy property syntax can be used instead of method syntax:
1 * subscriber.status // same as: 1 * subscriber.getStatus()
When expecting a call to a setter method, only method syntax can be used:
1 * subscriber.setStatus("ok") // NOT: 1 * subscriber.status = "ok"
Argument Constraints
The argument constraints of an interaction describe which method arguments are expected:
1 * subscriber.receive("hello") // an argument that is equal to the String "hello"
1 * subscriber.receive(!"hello") // an argument that is unequal to the String "hello"
1 * subscriber.receive() // the empty argument list (would never match in our example)
1 * subscriber.receive(_) // any single argument (including null)
1 * subscriber.receive(*_) // any argument list (including the empty argument list)
1 * subscriber.receive(!null) // any non-null argument
1 * subscriber.receive(_ as String) // any non-null argument that is-a String
1 * subscriber.receive(endsWith("lo")) // an argument matching the given Hamcrest matcher
// a String argument ending with "lo" in this case
1 * subscriber.receive({ it.size() > 3 && it.contains('a') })
// an argument that satisfies the given predicate, meaning that
// code argument constraints need to return true of false
// depending on whether they match or not
// (here: message length is greater than 3 and contains the character a)
Argument constraints work as expected for methods with multiple arguments:
1 * process.invoke("ls", "-a", _, !null, { ["abcdefghiklmnopqrstuwx1"].contains(it) })
When dealing with vararg methods, vararg syntax can also be used in the corresponding interactions:
interface VarArgSubscriber {
void receive(String... messages)
}
...
subscriber.receive("hello", "goodbye")
Equality Constraint
The equality constraint uses groovy equality to check the argument, i.e, argument == constraint
. You can use
-
any literal
1 * check('string')
/1 * check(1)
/1 * check(null)
, -
a variable
1 * check(var)
, -
a list or map literal
1 * check([1])
/1 * check([foo: 'bar'])
, -
an object
1 * check(new Person('sam'))
, -
or the result of a method call
1 * check(person())
as an equality constraint.
Hamcrest Constraint
A variation of the equality constraint, if the constraint object is a Hamcrest matcher, then it will use that matcher to check the argument.
Wildcard Constraint
The wildcard constraint will match any argument null
or otherwise. It is the , i.e.
1 * subscriber.receive()
.
There is also the spread wildcard constraint *_
which matches any number of arguments 1 * subscriber.receive(*_)
including none.
Code Constraint
The code constraint is the most versatile of all. It is a groovy closure that gets the argument as its parameter.
The closure is treated as an condition block, so it behaves like a then
block, i.e., every line is treated as an implicit assertion.
It can emulate all but the spread wildcard constraint, however it is suggested to use the simpler constraints where possible.
You can do multiple assertions, call methods for assertions, or use with
/verifyAll
.
1 * list.add({
verifyAll(it, Person) {
firstname == 'William'
lastname == 'Kirk'
age == 45
}
})
Negating Constraint
The negating constraint !
is a compound constraint, i.e. it needs to be combined with another constraint to work.
It inverts the result of the nested constraint, e.g, 1 * subscriber.receive(!null)
is the combination of
an equality constraint checking for null and then the negating constraint inverting the result, turning it into not null.
Although it can be combined with any other constraint it does not always make sense, e.g., 1 * subscriber.receive(!_)
will match nothing.
Also keep in mind that the diagnostics for a non matching negating constraint will just be that the inner
constraint did match, without any more information.
Type Constraint
The type constraint checks for the type/class of the argument, like the negating constraint it is also a compound constraint.
It usually written as _ as Type
, which is a combination of the wildcard constraint and the type constraint.
You can combined it with other constraints as well, 1 * subscriber.receive({ it.contains('foo')} as String)
will assert that it is
a String
before executing the code constraint to check if it contains foo
.
Matching Any Method Call
Sometimes it can be useful to match "anything", in some sense of the word:
1 * subscriber._(*_) // any method on subscriber, with any argument list
1 * subscriber._ // shortcut for and preferred over the above
1 * _._ // any method call on any mock object
1 * _ // shortcut for and preferred over the above
Note
|
Although (_.._) * _._(*_) >> _ is a valid interaction declaration,
it is neither good style nor particularly useful.
|
Strict Mocking
Now, when would matching any method call be useful? A good example is strict mocking, a style of mocking where no interactions other than those explicitly declared are allowed:
when:
publisher.publish("hello")
then:
1 * subscriber.receive("hello") // demand one 'receive' call on 'subscriber'
_ * auditing._ // allow any interaction with 'auditing'
0 * _ // don't allow any other interaction
0 *
only makes sense as the last interaction of a then:
block or method. Note the
use of _ *
(any number of calls), which allows any interaction with the auditing component.
Note
|
_ * is only meaningful in the context of strict mocking. In particular, it is never necessary
when Stubbing an invocation. For example, _ * auditing.record() >> "ok"
can (and should!) be simplified to auditing.record() >> "ok" .
|
Where to Declare Interactions
So far, we declared all our interactions in a then:
block. This often results in a spec that reads naturally.
However, it is also permissible to put interactions anywhere before the when:
block that is supposed to satisfy
them. In particular, this means that interactions can be declared in a setup
method. Interactions can also be
declared in any "helper" instance method of the same specification class.
When an invocation on a mock object occurs, it is matched against interactions in the interactions' declared order.
If an invocation matches multiple interactions, the earliest declared interaction that hasn’t reached its upper
invocation limit will win. There is one exception to this rule: Interactions declared in a then:
block are
matched against before any other interactions. This allows to override interactions declared in, say, a setup
method with interactions declared in a then:
block.
Declaring Interactions at Mock Creation Time
If a mock has a set of "base" interactions that don’t vary, they can be declared right at mock creation time:
Subscriber subscriber = Mock {
1 * receive("hello")
1 * receive("goodbye")
}
This feature is particularly attractive for Stubbing and with dedicated Stubs. Note that the interactions don’t (and cannot [2]) have a target constraint; it’s clear from the context which mock object they belong to.
Interactions can also be declared when initializing an instance field with a mock:
class MySpec extends Specification {
Subscriber subscriber = Mock {
1 * receive("hello")
1 * receive("goodbye")
}
}
Grouping Interactions with Same Target
Interactions sharing the same target can be grouped in a Specification.with
block. Similar to
Declaring Interactions at Mock Creation Time, this makes it unnecessary
to repeat the target constraint:
with(subscriber) {
1 * receive("hello")
1 * receive("goodbye")
}
A with
block can also be used for grouping conditions with the same target.
Mixing Interactions and Conditions
A then:
block may contain both interactions and conditions. Although not strictly required, it is customary
to declare interactions before conditions:
when:
publisher.send("hello")
then:
1 * subscriber.receive("hello")
publisher.messageCount == 1
Read out aloud: "When the publisher sends a 'hello' message, then the subscriber should receive the message exactly once, and the publisher’s message count should be one."
Explicit Interaction Blocks
Internally, Spock must have full information about expected interactions before they take place.
So how is it possible for interactions to be declared in a then:
block?
The answer is that under the hood, Spock moves interactions declared in a then:
block to immediately
before the preceding when:
block. In most cases this works out just fine, but sometimes it can lead to problems:
when:
publisher.send("hello")
then:
def message = "hello"
1 * subscriber.receive(message)
Here we have introduced a variable for the expected argument. (Likewise, we could have introduced a variable
for the cardinality.) However, Spock isn’t smart enough (huh?) to tell that the interaction is intrinsically
linked to the variable declaration. Hence it will just move the interaction, which will cause a
MissingPropertyException
at runtime.
One way to solve this problem is to move (at least) the variable declaration to before the when:
block. (Fans of Data Driven Testing might move the variable into a where:
block.)
In our example, this would have the added benefit that we could use the same variable for sending the message.
Another solution is to be explicit about the fact that variable declaration and interaction belong together:
when:
publisher.send("hello")
then:
interaction {
def message = "hello"
1 * subscriber.receive(message)
}
Since an MockingApi.interaction
block is always moved in its entirety, the code now works as intended.
Scope of Interactions
Interactions declared in a then:
block are scoped to the preceding when:
block:
when:
publisher.send("message1")
then:
1 * subscriber.receive("message1")
when:
publisher.send("message2")
then:
1 * subscriber.receive("message2")
This makes sure that subscriber
receives "message1"
during execution of the first when:
block,
and "message2"
during execution of the second when:
block.
Interactions declared outside a then:
block are active from their declaration until the end of the
containing feature method.
Interactions are always scoped to a particular feature method. Hence they cannot be declared in a static method,
setupSpec
method, or cleanupSpec
method. Likewise, mock objects should not be stored in static or @Shared
fields.
Verification of Interactions
There are two main ways in which a mock-based test can fail: An interaction can match more invocations than
allowed, or it can match fewer invocations than required. The former case is detected right when the invocation
happens, and causes a TooManyInvocationsError
:
Too many invocations for: 2 * subscriber.receive(_) (3 invocations)
To make it easier to diagnose why too many invocations matched, Spock will show all invocations matching the interaction in question:
Matching invocations (ordered by last occurrence): 2 * subscriber.receive("hello") <-- this triggered the error 1 * subscriber.receive("goodbye")
According to this output, one of the receive("hello")
calls triggered the TooManyInvocationsError
.
Note that because indistinguishable calls like the two invocations of subscriber.receive("hello")
are aggregated
into a single line of output, the first receive("hello")
may well have occurred before the receive("goodbye")
.
The second case (fewer invocations than required) can only be detected once execution of the when
block has completed.
(Until then, further invocations may still occur.) It causes a TooFewInvocationsError
:
Too few invocations for: 1 * subscriber.receive("hello") (0 invocations)
Note that it doesn’t matter whether the method was not called at all, the same method was called with different arguments,
the same method was called on a different mock object, or a different method was called "instead" of this one;
in either case, a TooFewInvocationsError
error will occur.
To make it easier to diagnose what happened "instead" of a missing invocation, Spock will show all invocations that didn’t match any interaction, ordered by their similarity with the interaction in question. In particular, invocations that match everything but the interaction’s arguments will be shown first:
Unmatched invocations (ordered by similarity):
1 * subscriber.receive("goodbye")
1 * subscriber2.receive("hello")
Invocation Order
Often, the exact method invocation order isn’t relevant and may change over time. To avoid over-specification, Spock defaults to allowing any invocation order, provided that the specified interactions are eventually satisfied:
then:
2 * subscriber.receive("hello")
1 * subscriber.receive("goodbye")
Here, any of the invocation sequences "hello"
"hello"
"goodbye"
, "hello"
"goodbye"
"hello"
, and
"goodbye"
"hello"
"hello"
will satisfy the specified interactions.
In those cases where invocation order matters, you can impose an order by splitting up interactions into
multiple then:
blocks:
then:
2 * subscriber.receive("hello")
then:
1 * subscriber.receive("goodbye")
Now Spock will verify that both "hello"
's are received before the "goodbye"
.
In other words, invocation order is enforced between but not within then:
blocks.
Note
|
Splitting up a then: block with and: does not impose any ordering, as and:
is only meant for documentation purposes and doesn’t carry any semantics.
|
Mocking Classes
Besides interfaces, Spock also supports mocking of classes and interfaces with different mock makers. Mocking classes works just like mocking interfaces, but you may need to put additional runtime dependencies on the class path, depending on the used mock maker.
Mock Instance Construction
When using for example
-
normal
Mock
s orStub
s or -
Spy
s that are configured withuseObjenesis: true
or -
Spy
s that spy on a concrete instance likeSpy(myInstance)
it is necessary to put org.objenesis:objenesis
3.0+ on the class path, except for classes with accessible
no-arg constructor or configured constructorArgs
unless the constructor call should not be done,
for example to avoid unwanted side effects.
If the library is missing from the class path, Spock will gently let you know.
Selecting a Mock Maker
The different mock makers have different capabilities and drawbacks.
You can manually select the used mock maker for the mocked object,
by specifying an IMockMakerSettings
instance as the mockMaker
mock option.
The class spock.mock.MockMakers
provides constants and methods for the built-in mock makers.
def subscriber = Mock(mockMaker: MockMakers.byteBuddy, Subscriber)
This will use the byte-buddy
mock maker to create the subscriber
.
Stubbing
Stubbing is the act of making collaborators respond to method calls in a certain way. When stubbing a method, you don’t care if and how many times the method is going to be called; you just want it to return some value, or perform some side effect, whenever it gets called.
For the sake of the following examples, let’s modify the Subscriber
's receive
method
to return a status code that tells if the subscriber was able to process a message:
interface Subscriber {
String receive(String message)
}
Now, let’s make the receive
method return "ok"
on every invocation:
subscriber.receive(_) >> "ok"
Read out aloud: "Whenever the subscriber receives a message, make it respond with 'ok'."
Compared to a mocked interaction, a stubbed interaction has no cardinality on the left end, but adds a response generator on the right end:
subscriber.receive(_) >> "ok" | | | | | | | response generator | | argument constraint | method constraint target constraint
A stubbed interaction can be declared in the usual places: either inside a then:
block, or anywhere before a
when:
block. (See Where to Declare Interactions for the details.) If a mock object is only used for stubbing,
it’s common to declare interactions at mock creation time or in a
given:
block.
Returning Fixed Values
We have already seen the use of the right-shift (>>
) operator to return a fixed value:
subscriber.receive(_) >> "ok"
To return different values for different invocations, use multiple interactions:
subscriber.receive("message1") >> "ok"
subscriber.receive("message2") >> "fail"
This will return "ok"
whenever "message1"
is received, and "fail"
whenever
"message2"
is received. There is no limit as to which values can be returned, provided they are
compatible with the method’s declared return type.
Returning Sequences of Values
To return different values on successive invocations, use the triple-right-shift (>>>
) operator:
subscriber.receive(_) >>> ["ok", "error", "error", "ok"]
This will return "ok"
for the first invocation, "error"
for the second and third invocation,
and "ok"
for all remaining invocations. The right-hand side must be a value that Groovy knows how to iterate over;
in this example, we’ve used a plain list.
Computing Return Values
To compute a return value based on the method’s argument, use the the right-shift (>>
) operator together with a closure.
If the closure declares a single untyped parameter, it gets passed the method’s argument list:
subscriber.receive(_) >> { args -> args[0].size() > 3 ? "ok" : "fail" }
Here "ok"
gets returned if the message is more than three characters long, and "fail"
otherwise.
In most cases it would be more convenient to have direct access to the method’s arguments. If the closure declares more than one parameter or a single typed parameter, method arguments will be mapped one-by-one to closure parameters:[3]
subscriber.receive(_) >> { String message -> message.size() > 3 ? "ok" : "fail" }
This response generator behaves the same as the previous one, but is arguably more readable.
If you find yourself in need of more information about a method invocation than its arguments, have a look at
org.spockframework.mock.IMockInvocation
. All methods declared in this interface are available inside the closure,
without a need to prefix them. (In Groovy terminology, the closure delegates to an instance of IMockInvocation
.)
Performing Side Effects
Sometimes you may want to do more than just computing a return value. A typical example is throwing an exception. Again, closures come to the rescue:
subscriber.receive(_) >> { throw new InternalError("ouch") }
Of course, the closure can contain more code, for example a println
statement. It
will get executed every time an incoming invocation matches the interaction.
Chaining Method Responses
Method responses can be chained:
subscriber.receive(_) >>> ["ok", "fail", "ok"] >> { throw new InternalError() } >> "ok"
This will return "ok", "fail", "ok"
for the first three invocations, throw InternalError
for the fourth invocations, and return ok
for any further invocation.
Returning a default response
If you don’t really care what you return, but you must return a non-null value, you can use _
.
This will use the same logic to compute a response as Stub
(see Stubs), so it is only really useful for Mock
and Spy
instances.
subscriber.receive(_) >> _
You can of course use this with chaining as well. Here it might be useful for Stub
instances as well.
subscriber.receive(_) >>> ["ok", "fail"] >> _ >> "ok"
An application of this is to let a Mock
behave like a Stub
, but still be able to do assertions.
The default response will return the mock itself, if the return type of the method is assignable from the mock type (excluding object).
This is useful when dealing with fluent APIs, like builders, which are otherwise really painful to mock.
given:
ThingBuilder builder = Mock() {
_ >> _
}
when:
Thing thing = builder
.id("id-42")
.name("spock")
.weight(100)
.build()
then:
1 * builder.build() >> new Thing(id: 'id-1337')
thing.id == 'id-1337'
The _ >> _
instructs the mock to return the default response for all interactions.
However, interactions defined in the then
block will have precedence over the interactions defined in the given
block,
this lets us override and assert the interaction we actually care about.
Combining Mocking and Stubbing
Mocking and stubbing go hand-in-hand:
1 * subscriber.receive("message1") >> "ok"
1 * subscriber.receive("message2") >> "fail"
When mocking and stubbing the same method call, they have to happen in the same interaction. In particular, the following Mockito-style splitting of stubbing and mocking into two separate statements will not work:
given:
subscriber.receive("message1") >> "ok"
when:
publisher.send("message1")
then:
1 * subscriber.receive("message1")
As explained in Where to Declare Interactions, the receive
call will first get matched against
the interaction in the then:
block. Since that interaction doesn’t specify a response, the default
value for the method’s return type (null
in this case) will be returned. (This is just another
facet of Spock’s lenient approach to mocking.). Hence, the interaction in the given:
block will never
get a chance to match.
Note
|
Mocking and stubbing of the same method call has to happen in the same interaction. |
Other Kinds of Mock Objects
So far, we have created mock objects with the MockingApi.Mock
method. Aside from
this method, the MockingApi
class provides a couple of other factory methods for creating
more specialized kinds of mock objects.
Stubs
A stub is created with the MockingApi.Stub
factory method:
Subscriber subscriber = Stub()
Whereas a mock can be used both for stubbing and mocking, a stub can only be used for stubbing. Limiting a collaborator to a stub communicates its role to the readers of the specification.
Note
|
If a stub invocation matches a mandatory interaction (like 1 * foo.bar() ), an InvalidSpecException is thrown.
|
Like a mock, a stub allows unexpected invocations. However, the values returned by a stub in such cases are more ambitious:
-
For primitive types, the primitive type’s default value is returned.
-
For non-primitive numerical values (such as
BigDecimal
), zero is returned. -
If the value is assignable from the stub instance, then the instance is returned (e.g. builder pattern)
-
For non-numerical values, an "empty" or "dummy" object is returned. This could mean an empty String, an empty collection, an object constructed from its default constructor, or another stub returning default values. See class
org.spockframework.mock.EmptyOrDummyResponse
for the details.
Note
|
If the response type of the method is a final class or if it requires a class-mocking library and cglib or ByteBuddy
are not available, then the "dummy" object creation will fail with a CannotCreateMockException .
|
A stub often has a fixed set of interactions, which makes declaring interactions at mock creation time particularly attractive:
Subscriber subscriber = Stub {
receive("message1") >> "ok"
receive("message2") >> "fail"
}
Spies
(Think twice before using this feature. It might be better to change the design of the code under specification.)
A spy is created with the MockingApi.Spy
factory method:
SubscriberImpl subscriber = Spy(constructorArgs: ["Fred"])
A spy is always based on a real object. Hence you must provide a class type rather than an interface type, along with any constructor arguments for the type. If no constructor arguments are provided, the type’s no-arg constructor will be used.
If the given constructor arguments lead to an ambiguity, you can cast the constructor
arguments as usual using as
or Java-style cast. If the testee for example has
one constructor with a String
parameter and one with a Pattern
parameter
and you want null
as constructorArg
:
SubscriberImpl subscriber = Spy(constructorArgs: [null as String])
SubscriberImpl subscriber2 = Spy(constructorArgs: [(Pattern) null])
You may also create a spy from an instantiated object. This may be useful in cases where you do not have full control over the instantiation of types you are interested in spying. (For example when testing within a Dependency Injection framework such as Spring or Guice.)
Method calls on a spy are automatically delegated to the real object. Likewise, values returned from the real object’s methods are passed back to the caller via the spy.
After creating a spy, you can listen in on the conversation between the caller and the real object underlying the spy:
1 * subscriber.receive(_)
Apart from making sure that receive
gets called exactly once,
the conversation between the publisher and the SubscriberImpl
instance underlying the spy remains unaltered.
When stubbing a method on a spy, the real method no longer gets called:
subscriber.receive(_) >> "ok"
Instead of calling SubscriberImpl.receive
, the receive
method will now simply return "ok"
.
Sometimes, it is desirable to both execute some code and delegate to the real method:
subscriber.receive(_) >> { String message -> callRealMethod(); message.size() > 3 ? "ok" : "fail" }
Here we use callRealMethod()
to delegate the method invocation to the real object.
Note that we don’t have to pass the message
argument along; this is taken care of automatically. callRealMethod()
returns the real invocation’s result, but in this example we opted to return our own result instead.
If we had wanted to pass a different message to the real method, we could have used callRealMethodWithArgs("changed message")
.
Please note that while semantically both callRealMethod()
and callRealMethodWithArgs(…)
only make sense with spies,
technically you can also call these methods on mock or stub objects, kind of turning them into (pseudo) spy objects
"through the backdoor". The only precondition is that the mocked/stubbed object actually has a real method implementation,
i.e. for interface mocks there must be a default method, for class mocks there must be a (non-abstract) original method.
Partial Mocks
(Think twice before using this feature. It might be better to change the design of the code under specification.)
Spies can also be used as partial mocks:
// this is now the object under specification, not a collaborator
MessagePersister persister = Spy {
// stub a call on the same object
isPersistable(_) >> true
}
when:
persister.receive("msg")
then:
// demand a call on the same object
1 * persister.persist("msg")
Groovy Mocks
So far, all the mocking features we have seen work the same no matter if the calling code is written in Java or Groovy.
By leveraging Groovy’s dynamic capabilities, Groovy mocks offer some additional features specifically for testing Groovy code.
They are created with the MockingApi.GroovyMock()
, MockingApi.GroovyStub()
, and MockingApi.GroovySpy()
factory methods.
Tip
|
When Should Groovy Mocks be Favored over Regular Mocks? Groovy mocks should be used when the code under specification is written in Groovy and some of the unique Groovy mock features are needed. When called from Java code, Groovy mocks will behave like regular mocks. Note that it isn’t necessary to use a Groovy mock merely because the code under specification and/or mocked type is written in Groovy. Unless you have a concrete reason to use a Groovy mock, prefer a regular mock. |
Mocking Dynamic Methods
All Groovy mocks implement the GroovyObject
interface. They support the mocking and stubbing of
dynamic methods as if they were physically declared methods:
Subscriber subscriber = GroovyMock()
1 * subscriber.someDynamicMethod("hello")
Mocking All Instances of a Type
(Think twice before using this feature. It might be better to change the design of the code under specification.)
Usually, Groovy mocks need to be injected into the code under specification just like regular mocks. However, when a Groovy mock is created as global, it automagically replaces all real instances of the mocked type for the duration from mock creation up until the end of the feature method:[4]
given:
def publisher = new Publisher()
def anySubscriber = GroovySpy(global: true, RealSubscriber)
publisher.subscribers.add(new RealSubscriber())
publisher.subscribers.add(new RealSubscriber())
when:
publisher.send("message")
then:
2 * anySubscriber.receive("message")
Here, we set up the publisher with two instances of a real subscriber implementation. Then we create a global mock of the same type. This reroutes all method calls on the real subscribers to the mock object. The mock object’s instance isn’t ever passed to the publisher; it is only used to describe the interaction.
Note
|
A global mock can only be created for a class type. It effectively replaces all instances of that type for the duration from mock creation up until the end of the feature method. |
Caution
|
Using global mocks for standard types from the JDK, for example ArrayList , is a bad idea and can lead to unforeseen and widespread consequences.
|
Caution
|
The declaration order of global mocks is relevant.
The GroovySpy(global:true, <type>) must come before all creations of new mocked/spied objects of <type> .
The global spies will only take effect on objects of that type, if the GroovySpy(global:true, <type>) was
executed before the new <type> .
|
Since global mocks have a somewhat, well, global effect, it’s often convenient
to use them together with GroovySpy
. This leads to the real code getting
executed unless an interaction matches, allowing you to selectively listen
in on objects and change their behavior just where needed.
Caution
|
When using GroovyMock(global:true) it will also replace constructor calls, which will return null by default.
If you want working real constructors, please use GroovySpy instead.
|
given:
GroovyMock(global: true, RealSubscriber)
when:
def sub = new RealSubscriber()
then: "The GroovyMock(global: true) will also mock the constructor"
sub == null
If you want, that the real constructors are called:
given:
GroovyMock(global: true, RealSubscriber) {
//Allow that the real constructor is called
new RealSubscriber(*_) >> { callRealMethod() }
}
when:
def sub = new RealSubscriber()
then:
sub instanceof RealSubscriber
When Specifications
or Features
are executed concurrently you have to make sure that the Features
which create
global mocks on the same types are properly guarded against each other, because a global mock changes the global state
for the mocked Class
during execution.
Global mocks and parallel execution
Creating a global GroovyMock
/GroovyStub
/GroovySpy
when parallel execution is enabled,
requires that the spec is annotated with @Isolated or @ResourceLock(org.spockframework.runtime.model.parallel.Resources.META_CLASS_REGISTRY)
.
If it is only used for a feature, then it suffices that the feature is annotated with @ResourceLock(org.spockframework.runtime.model.parallel.Resources.META_CLASS_REGISTRY)
.
The rule of thumb to choose between @ResourceLock
and @Isolated
, is to look at how widespread the mocked type is used.
If it is widely used, then @Isolated
is the safe choice, otherwise @ResourceLock
may be sufficient.
Mocking Constructors
(Think twice before using this feature. It might be better to change the design of the code under specification.)
Global mocks support mocking of constructors:
RealSubscriber anySubscriber = GroovySpy(global: true)
1 * new RealSubscriber("Fred")
Since we are using a spy, the object returned from the constructor call remains unchanged. To change which object gets constructed, we can stub the constructor:
new RealSubscriber("Fred") >> new RealSubscriber("Barney")
Now, whenever some code tries to construct a subscriber named Fred, we’ll construct a subscriber named Barney instead.
Mocking Static Methods
Spock supports two ways of mocking static methods:
-
SpyStatic()
static mocks: Works with Java and Groovy, but requires a mock maker supporting this, e.g. mockito Mock Maker. -
Global Groovy mocks: Work only for Groovy code not for Java. If parallel execution is enabled, then the test must be annotated with
@ResourceLock(Resources.META_CLASS_REGISTRY)
or@Isolated
.
Note
|
Think twice before using this feature. It might be better to change the design of the code under specification. |
Static Mocks
You can create static mocks with SpyStatic()
.
The semantics are the same as for the non-static variants:
-
SpyStatic()
: Mocks static methods of the given type; by default, delegates all calls to the real static methods. Supports both stubbing and mocking.
We are using the class StaticClass
in the examples:
private static class StaticClass {
static String staticMethod() {
return "RealValue"
}
}
We want to mock the method staticMethod()
, so we can create a static mock for the StaticClass
type:
given:
SpyStatic(StaticClass)
expect: "By default SpyStatic() will return the real value"
StaticClass.staticMethod() == "RealValue"
when:
def result = StaticClass.staticMethod()
then:
1 * StaticClass.staticMethod() >> "MockValue"
result == "MockValue"
You can also specify the answers after construction by defining the interactions as for a normal mock:
given:
SpyStatic(StaticClass)
StaticClass.staticMethod() >> "MockValue"
expect:
StaticClass.staticMethod() == "MockValue"
Note
|
The static mocks require a mock maker supporting static methods, e.g. mockito. See mock makers table for mock makers supporting it. |
Static Mocks and Threading
The static mocks are thread-local, so they do not interfere with concurrent test execution. But this also means that a static mock will not be active, if your code under test will use other threads.
A static mock is activated on the thread, which created the mock, up until the feature execution ends. You can activate all static mocks on a different thread by hand:
given:
SpyStatic(StaticClass)
StaticClass.staticMethod() >> "MockValue"
when:
def executor = Executors.newSingleThreadExecutor()
def wasCalled = false
def future = executor.submit {
assert StaticClass.staticMethod() == "RealValue"
withActiveThreadAwareMocks {
//Now the static methods of StaticClass are mocked:
assert StaticClass.staticMethod() == "MockValue"
wasCalled = true
}
assert StaticClass.staticMethod() == "RealValue"
}
future.get()
then:
StaticClass.staticMethod() == "MockValue"
wasCalled
cleanup:
executor.shutdown()
The interface spock.mock.IStaticMock
provides API to activate a static mock on other threads.
Global Groovy Mocks for Static Methods
Global mocks support mocking and stubbing of static methods:
RealSubscriber anySubscriber = GroovySpy(global: true)
1 * RealSubscriber.someStaticMethod("hello") >> 42
The same works for dynamic static methods.
When a global mock is used solely for mocking constructors and static methods, the mock’s instance isn’t really needed. In such a case one can just write:
GroovySpy(RealSubscriber, global: true)
Note
|
Global mocks will only work for Groovy code under test not for Java code. |
Advanced Features
Most of the time you shouldn’t need these features. But if you do, you’ll be glad to have them.
A la Carte Mocks
At the end of the day, the Mock()
, Stub()
, and Spy()
factory methods are just canned ways to
create mock objects with a certain configuration. If you want more fine-grained control over a mock’s configuration,
have a look at the org.spockframework.mock.IMockConfiguration
interface. All properties of this interface
[5]
can be passed as named arguments to the Mock()
method. For example:
def person = Mock(name: "Fred", type: Person, defaultResponse: ZeroOrNullResponse.INSTANCE, verified: false)
Here, we create a mock whose default return values match those of a Mock()
, but whose invocations aren’t
verified (as for a Stub()
). Instead of passing ZeroOrNullResponse
, we could have supplied our own custom
org.spockframework.mock.IDefaultResponse
for responding to unexpected method invocations.
Detecting Mock Objects
To find out whether a particular object is a Spock mock object, use a org.spockframework.mock.MockUtil
:
MockUtil mockUtil = new MockUtil()
List list1 = []
List list2 = Mock()
expect:
!mockUtil.isMock(list1)
mockUtil.isMock(list2)
An util can also be used to get more information about a mock object:
IMockObject mock = mockUtil.asMock(list2)
expect:
mock.name == "list2"
mock.type == List
mock.nature == MockNature.MOCK
Create Mocks Outside Specifications
Sometimes, it can be helpful to create and possibly preconfigure mock, stub or spy objects outside specifications, especially if you want to factor out duplicate or similar code from there.
Use DetachedMockFactory
to create such mock objects.
Note
|
Detached mocks, as the name implies, must be attached to a specification to make them functional.
This either happens manually by calling MockUtil.attachMock(Object, Specification) or automatically by declaring the mock as an instance field with an @AutoAttach annotation, see AutoAttach.
While auto-attached mocks will also be auto-detached during specification clean-up, you need to take care of manually attached mocks by yourself, calling MockUtil.detachMock(Object) when they are no longer in use.
Spring and Guice dependency injection will also automatically attach mocks, when the SpringExtension or GuiceExtension is used.
|
Assuming that we have an application class Car
and each car has an Engine
:
class Engine {
private boolean started
boolean isStarted() { return started }
void start() { started = true }
void stop() { started = false }
}
class Car {
private Engine engine
void drive() { engine.start() }
void park() { engine.stop() }
}
In our specification, we declare a detached mock factory and a mock util, either inside a feature method or, if we need them for multiple features, as (preferably) shared or (suboptimally) static instances:
@Shared
def mockFactory = new DetachedMockFactory()
@Shared
def mockUtil = new MockUtil()
Manually attach / detach
Now in our feature, we create a detached Engine
mock, attach the mock to the specification manually, stub its isStarted()
method, inject the mock into a Car
subject under specification, use the subject and finally detach the mock again after use:
def "Manually attach detached mock"() {
given:
def manuallyAttachedEngine = mockFactory.Mock(Engine)
mockUtil.attachMock(manuallyAttachedEngine, this)
manuallyAttachedEngine.isStarted() >> true
def car = new Car(engine: manuallyAttachedEngine)
when:
car.drive()
then:
1 * manuallyAttachedEngine.start()
manuallyAttachedEngine.isStarted()
when:
car.park()
then:
1 * manuallyAttachedEngine.stop()
manuallyAttachedEngine.isStarted()
cleanup:
mockUtil.detachMock(manuallyAttachedEngine)
}
Use @AutoAttach
Same situation, different approach: We let Spock take care of automatically attaching the detached mock when starting the feature method and detaching it again after running the feature.
@AutoAttach
def autoAttachedEngine = mockFactory.Mock(Engine)
def "Auto-attach detached mock"() {
given:
autoAttachedEngine.isStarted() >> true
def car = new Car(engine: autoAttachedEngine)
when:
car.drive()
then:
1 * autoAttachedEngine.start()
autoAttachedEngine.isStarted()
when:
car.park()
then:
1 * autoAttachedEngine.stop()
autoAttachedEngine.isStarted()
}
Pre-configure detached mock with default response
This advanced use case might only be of interest to a minority of users, you may skip it for now if you just want to learn Spock basics.
Usually, you would simply create your detached mock outside of Spock, but then define interactions (stubbed method results, method call verifications) inside the feature method itself. Sometimes however, you might want to define some (user-overridable) default behaviour right in the detached mock definition itself and even make it somewhat configurable.
A valid use case could be a detached mock used in a dependency injection framework like Spring or Guice.
The framework might wire the mock and rely on some default behaviour, before Spock even gets a chance to stub any methods.
Defining default behaviour is possible using a combination of Spock’s mocking API and custom IDefaultResponse
implementations.
A custom detached mock creator could look like this (please figure out by yourself what it does and how it works):
class EngineMockCreator {
enum StartMode {
ALWAYS_STARTED, ALWAYS_STOPPED, RANDOMLY_STARTED, REAL_RESPONSE
}
static DetachedMockFactory mockFactory = new DetachedMockFactory()
static class EngineStateResponse implements IDefaultResponse {
StartMode startMode
@Override
Object respond(IMockInvocation invocation) {
if (invocation.method.name != 'isStarted')
return ZeroOrNullResponse.INSTANCE.respond(invocation)
startMode == RANDOMLY_STARTED
? ThreadLocalRandom.current().nextBoolean()
: startMode == ALWAYS_STARTED
}
}
static Engine getMock(StartMode startMode) {
startMode == REAL_RESPONSE
? mockFactory.Spy(new Engine())
: mockFactory.Mock(Engine, defaultResponse: new EngineStateResponse(startMode: startMode)) as Engine
}
}
The first parametrized feature uses the mock without attach:
@Unroll("Engine state #engineStateResponseType")
def "Mock usage without manually attach detach with preconfigured engine state"() {
given:
def car = new Car(engine: preconfiguredEngine)
// The preconfigured mock with default behaviour behaves as defined,
// even *without* attaching it to the spec.
when:
car.drive()
then:
possibleResponsesAfterStart.contains(preconfiguredEngine.isStarted())
when:
car.park()
then:
possibleResponsesAfterStop.contains(preconfiguredEngine.isStarted())
where:
engineStateResponseType | possibleResponsesAfterStart | possibleResponsesAfterStop
ALWAYS_STARTED | [true] | [true]
ALWAYS_STOPPED | [false] | [false]
RANDOMLY_STARTED | [true, false] | [true, false]
REAL_RESPONSE | [true] | [false]
preconfiguredEngine = EngineMockCreator.getMock(engineStateResponseType)
}
The next parametrized feature method showcasing a set of usage scenarios when a detached mock is attached to a spec before usage:
@Unroll("Engine state #engineStateResponseType")
def "Manually attach detached mock with preconfigured engine state"() {
given:
def car = new Car(engine: preconfiguredEngine)
//Now, let's attach the mock to the spec and override its default behaviour.
mockUtil.attachMock(preconfiguredEngine, this)
preconfiguredEngine.isStarted() >> true
expect:
preconfiguredEngine.isStarted()
// The attached mock now behaves differently. Because it has been attached to the
// spec, we can also verify interactions using '1 * ...' or similar, which
// would not be possible without attaching it.
when:
car.drive()
then:
1 * preconfiguredEngine.start()
preconfiguredEngine.isStarted()
when:
car.park()
then:
1 * preconfiguredEngine.stop()
preconfiguredEngine.isStarted()
cleanup:
mockUtil.detachMock(preconfiguredEngine)
where:
engineStateResponseType | possibleResponsesAfterStart | possibleResponsesAfterStop
ALWAYS_STARTED | [true] | [true]
ALWAYS_STOPPED | [false] | [false]
RANDOMLY_STARTED | [true, false] | [true, false]
REAL_RESPONSE | [true] | [false]
preconfiguredEngine = EngineMockCreator.getMock(engineStateResponseType)
}
Further Reading
If you would like to dive deeper into interaction-based testing, we recommend the following resources:
- Endo-Testing: Unit Testing with Mock Objects
-
Paper from the XP2000 conference that introduces the concept of mock objects.
- Mock Roles, not Objects
-
Paper from the OOPSLA2004 conference that explains how to do mocking right.
- Mocks Aren’t Stubs
-
Martin Fowler’s take on mocking.
- Growing Object-Oriented Software Guided by Tests
-
TDD pioneers Steve Freeman and Nat Pryce explain in detail how test-driven development and mocking work in the real world.