Spocklight: Custom Default Responses for Stubs
Although I couldn't make it to Gr8Conf EU this year, I am glad a lot of the presentations are available as slide decks and videos.
The slide deck for the talk Interesting nooks and crannies of Spock you (may) have never seen before by Marcin Zajączkowski is very interesting.
This is really a must read if you use Spock (and why shouldn't you) in your projects.
One of the interesting things is the ability to change the response for methods in a class that is stubbed using Spock's Stub
method, but have no explicit stubbed method definition.
So normally when we create a stub we would add code that implements the methods from the stubbed class.
In our specification the methods we have written are invoked instead of the original methods from the stubbed class.
By default if we don't override a method definition, but it is used in the specification, Spock will try to create a response using a default response strategy.
The default response strategy for a stub is implemented by the class EmptyOrDummyResponse
.
For example if a method has a return type Message
then Spock will create a new instance of Message
and return it to be used in the specification.
Spock also has a ZeroOrNullResponse
response strategy.
With this strategy null
is returned for our method that returns the Message
type.
Both response strategies implement the IDefaultResponse
interface.
We can write our own response strategy by implementing this interface.
When we use the Stub
method we can pass an instance of our response strategy with the defaultResponse
named argument of the method.
For example: MessageProvider stub = Stub(defaultResponse: new CustomResponse())
.
We implement the respond
method of IDefaultResponse
to write a custom response strategy.
The method gets a IMockInvocation
instance.
We can use this instance to check for example the method name, return type, arguments and more.
Based on this we can write code to return the response we want.
In the following example we have a Spock specification where we create a stub using the default response strategy, the ZeroOrNullResponse
strategy and a custom written response strategy:
package com.mrhaki.spock
@Grab('org.spockframework:spock-core:1.0-groovy-2.4')
import spock.lang.Specification
import spock.lang.Subject
import org.spockframework.mock.ZeroOrNullResponse
import org.spockframework.mock.IDefaultResponse
import org.spockframework.mock.IMockInvocation
class SampleSpec extends Specification {
def """stub default response returns
instance of Message created with default constructor"""() {
given: 'Use default response strategy EmptyOrDummyResponse'
final MessageProvider messageProvider = Stub()
final Sample sample = new Sample(messageProvider)
expect:
sample.sampleMessage == 'Sample says: default'
}
def "stub default reponse returns null with ZeroOrNullResponse"() {
given: 'Use default response strategy of ZeroOrNullResponse'
final MessageProvider messageProvider =
Stub(defaultResponse: ZeroOrNullResponse.INSTANCE)
final Sample sample = new Sample(messageProvider)
when:
sample.sampleMessage
then: 'messageProvider.message returns null'
thrown(NullPointerException)
}
def """stub default response returns
Message object with initialized text property
from StubMessageResponse"""() {
given: 'Use custom default response strategy'
final MessageProvider messageProvider =
Stub(defaultResponse: new StubMessageResponse())
final Sample sample = new Sample(messageProvider)
expect:
sample.sampleMessage == 'Sample says: *STUB MESSAGE TEXT*'
}
}
/**
* Class to test with a dependency on MessageProvider
* that is stubbed in the specification.
*/
class Sample {
private final MessageProvider messageProvider
Sample(final MessageProvider messageProvider) {
this.messageProvider = messageProvider
}
String getSampleMessage() {
"Sample says: ${messageProvider.message.text}"
}
String sampleMessage(String prefix) {
"Sample says: ${messageProvider.getMessageWithPrefix(prefix).text}"
}
}
/**
* Work with messages. This interface is stubbed
* in the specification.
*/
interface MessageProvider {
Message getMessage()
Message getMessageWithPrefix(String prefix)
}
/**
* Supporting class for MessageProvider.
*/
class Message {
String text = 'default'
}
/**
* Custom default response strategy.
* When a method has a Message return type then we
* create an instance of Message with a custom text
* property value.
* Otherwise rely on default behaviour.
*/
class StubMessageResponse implements IDefaultResponse {
@Override
Object respond(IMockInvocation invocation) {
// If return type of method is Message we create
// a new Message object with a filled text property.
if (invocation.method.returnType == Message) {
return new Message(text: '*STUB MESSAGE TEXT*')
}
// Otherwise use default response handler for Stubs.
return ZeroOrNullResponse.INSTANCE.respond(invocation)
}
}
Written with Spock 1.0-groovy-2.4.