Management summary
If you do not need to verify interactions like 1 * myMock.doSomething("foo"), you can use a Stub instead of a Mock, because while mocks always return null, false or 0, stubs return a more sophisticated default response, e.g. empty objects rather than null and - most importantly - the stub itself for methods with a return type matching the stubbed type. I.e., testing fluent APIs with stubs is easy.
If however you wish to also verify interactions, you cannot use a Stub and have to use a Mock instead. But there the default response is null, i.e. you need to override it for the fluent API methods. This is quite easy in both In Spock 1.x and 2.x. Specifically in 2.x, there is some syntactic sugar for it, making the code even smaller.
Classes under test
Quick & dirty implementation, just for illustration:
package de.scrum_master.stackoverflow.q57298557
import groovy.transform.ToString
@ToString(includePackage = false)
class Person {
String name
int age
String hair
}
package de.scrum_master.stackoverflow.q57298557
class PersonBuilder {
Person person = new Person()
PersonBuilder withAge(int age) {
person.age = age
this
}
PersonBuilder withName(String name) {
person.name = name
this
}
PersonBuilder withHair(String hair) {
person.hair = hair
this
}
Person build() {
person
}
}
Test code
Testing the original class, no mocking
package de.scrum_master.stackoverflow.q57298557
import spock.lang.Specification
class PersonBuilderTest extends Specification {
def "create person with real builder"() {
given:
def personBuilder = new PersonBuilder()
when:
def person = personBuilder
.withHair("blonde")
.withAge(22)
.withName("Alice")
.build()
then:
person.age == 22
person.hair == "blonde"
person.name == "Alice"
}
}
Simple stubbing without interaction testing
This is the simple case and works for both Spock 1.x and 2.x. Add this feature method to your Spock specification:
def "create person with stub builder, no interactions"() {
given:
PersonBuilder personBuilder = Stub()
personBuilder.build() >> new Person(name: "John Doe", age: 99, hair: "black")
when:
def person = personBuilder
.withHair("blonde")
.withAge(22)
.withName("Alice")
.build()
then:
person.age == 99
person.hair == "black"
person.name == "John Doe"
}
Mock with custom default response
Just tell Spock to use stub-like default responses for your mock:
import org.spockframework.mock.EmptyOrDummyResponse
// ...
def "create person with mock builder, use interactions"() {
given:
PersonBuilder personBuilder = Mock(defaultResponse: EmptyOrDummyResponse.INSTANCE)
when:
def person = personBuilder
.withHair("blonde")
.withAge(22)
.withName("Alice")
.build()
then:
3 * personBuilder./with.*/(_)
1 * personBuilder.build() >> new Person(name: "John Doe", age: 99, hair: "black")
person.age == 99
person.hair == "black"
person.name == "John Doe"
}
The syntax above works for bork Spock 1.x and 2.x. Since version 2.0-M3, Spock users can tell their mocks/spies to return a stub-like default response using the syntactic sugar syntax >> _, e.g. in the simplest case
Mock() {
_ >> _
}
Thanks to Spock maintainer Leonard Brünings for sharing this neat little trick.
Then later in the then: or expect: block, you can still define additional interactions and stub responses, overriding the default. In your case, it could look like this:
import spock.lang.Requires
import org.spockframework.util.SpockReleaseInfo
//...
@Requires({ SpockReleaseInfo.version.major >= 2})
def "create person with mock builder, use interactions, Spock 2.x"() {
given:
PersonBuilder personBuilder = Mock()
when:
def person = personBuilder
.withHair("blonde")
.withAge(22)
.withName("Alice")
.build()
then:
3 * personBuilder./with.*/(_) >> _
1 * personBuilder.build() >> new Person(name: "John Doe", age: 99, hair: "black")
person.age == 99
person.hair == "black"
person.name == "John Doe"
}
Original answer
Before I realised that Spock's own EmptyOrDummyResponse, which is used by stubs by default, actually returns the mock instance for methods matching with a return type matching the mocked/stubbed class, I thought it would just return an empty object like for methods with other return types, i.e. empty strings, collections etc. Therefore, I invented my own ThisResponse type. Even though it is not necessary here, I am keeping the old solution, because it teaches users how to implement and use custom default responses.
If you want a generic solution for builder classes, you can use à la carte mocks as described in the Spock manual. A little caveat: The manual specifies a custom IDefaultResponse type parameter when creating the mock, but you need to specify an instance of that type instead.
Here we have our custom IDefaultResponse which makes the default response for mock calls not null, zero or an empty object, but the mock instance itself. This is ideal for mocking builder classes with fluent interfaces. You just need to make sure to stub the build() method to actually return the object to be built, not the mock. For example, PersonBuilder.build() should not return the default PersonBuilder mock but a Person.
package de.scrum_master.stackoverflow.q57298557
import org.spockframework.mock.IDefaultResponse
import org.spockframework.mock.IMockInvocation
class ThisResponse implements IDefaultResponse {
public static final ThisResponse INSTANCE = new ThisResponse()
private ThisResponse() {}
@Override
Object respond(IMockInvocation invocation) {
invocation.mockObject.instance
}
}
Now you can use ThisResponse in your mocks as follows:
def "create person with a la carte mock builder, use interactions"() {
given:
PersonBuilder personBuilder = Mock(defaultResponse: ThisResponse.INSTANCE) {
3 * /with.*/(_)
1 * build() >> new Person(name: "John Doe", age: 99, hair: "black")
}
when:
def person = personBuilder
.withHair("blonde")
.withAge(22)
.withName("Alice")
.build()
then:
person.age == 99
person.hair == "black"
person.name == "John Doe"
}