How to mock a builder with mockito
Asked Answered
O

3

43

I have a builder:

class Builder{
     private String name;
     private String address;
     public Builder setName(String name){
         this.name = name;
         return this;
    }
     public Builder setAddress(String address){
         this.address = address;
         return this;
    }

}

Mocking the builder in mockito will gives me null for every method. So is there an easy way to get the builder return itself on every function call, without mocking every function itself using when().thenReturn.

Oni answered 14/12, 2011 at 9:11 Comment(3)
Do you really need to mock this? It doesn't look like the sort of dependency which is worth mocking. It looks like a "data" kind of class, rather than a "service" kind of class. I rarely find it's useful to make classes where there isn't much actual behaviour.Knotting
Its just an example, the real builder is a bit more complex and it just dont need to test in this case.Stanford
Can you separate them so that you have got a "dumb builder" (which doesn't need mocking) and then the service part which does need mocking?Knotting
R
24

You can use RETURN_DEEP_STUBS to mock a chaining API.

If you know the exact order your builder will be called, here's an example of how you would use it:

Builder b = Mockito.mock(Builder.class, RETURNS_DEEP_STUBS);
when(b.setName("a name").setAddress("an address")).thenReturn(b);
assert b.setName("a name").setAddress("an address") == b; // this passes

Unfortunately this won't give you a generic way of mocking "all the various builder methods" so that they always return this, see the other answer is you need that.

Radiosurgery answered 14/12, 2011 at 10:5 Comment(3)
Also NB that if you get the deep stub ordering "wrong" and cast the result, it will give you some odd looking messages, possibly like java.lang.ClassCastException: org.mockito.internal.creation.jmock.ClassImposterizer$ClassWithSuperclassToWorkAroundCglibBug$$EnhancerByMockitoWithCGLIB$$851828bd cannot be cast to ...Melee
I'd add that verify does not work when using RETURN_DEEP_STUBSApnea
Alternatively, if using annotations: @Mock(answer = Answers.RETURNS_DEEP_STUBS) Builder bIngleside
C
57

The problem with using RETURN_DEEP_STUBS is that you'll get a different mock each time you call a method. I think from your question that you want to use a default Answer that actually returns the mock on which it was called, for each method that has the right return type. This could look something like the following. Note that I haven't tested this, so it may contain typos, but I hope that the intention is clear in any case.

import static org.mockito.Mockito.RETURNS_DEFAULTS;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class SelfReturningAnswer implements Answer<Object> {

    public Object answer(InvocationOnMock invocation) throws Throwable {
        Object mock = invocation.getMock();
        if(invocation.getMethod().getReturnType().isInstance(mock)){
            return mock;
        }
        return RETURNS_DEFAULTS.answer(invocation);
    }
}

Then, when you create your mock, specify this as your default answer. This will make your mock return itself from each method that it can; but it will behave like an ordinary mock when you call a method whose return type is wrong for the mock.

Create your mock like this

Builder mockBuilder = mock( Builder.class, new SelfReturningAnswer());

or create a constant for this class and write something like

@Mock(answer = SELF_RETURNING) private Builder mockBuilder;

Hope that helps.

Conventioner answered 16/12, 2011 at 5:32 Comment(4)
Are your sure. I use ´verify´ on my deep stubbed mock and it pass the tests. So it must return the same instance every time I use a builder method.Stanford
Yes, I'm absolutely sure. I wrote a test for your Builder class, as above, where I made a mock with RETURNS_DEEP_STUBS, then called setAddress and setName. My test asserted that the two mocks returned from the two method calls were different. My test passed.Conventioner
appears what this answer describes is a popular way to do it...something similar is mentioned here jakegoulding.com/blog/2012/01/09/…Melee
@shiv455 singleton for exampleEllora
S
47

As of Mockito 2.0 (beta), there is a new default answer for RETURNS_SELF that behaves almost identically to David Wallace's answer. Example from the Mockito docs:

@Test
public void use_full_builder_with_terminating_method() {
    HttpBuilder builder = mock(HttpBuilder.class, RETURNS_SELF);
    HttpRequesterWithHeaders requester = new HttpRequesterWithHeaders(builder);
    String response = "StatusCode: 200";

    when(builder.request()).thenReturn(response);

    assertThat(requester.request("URI")).isEqualTo(response);
}

Note that it appears both on the Mockito class and on the Answers enum, so it is also be compatible with @Mock(answer = RETURNS_SELF) syntax.

Stu answered 24/3, 2016 at 16:27 Comment(0)
R
24

You can use RETURN_DEEP_STUBS to mock a chaining API.

If you know the exact order your builder will be called, here's an example of how you would use it:

Builder b = Mockito.mock(Builder.class, RETURNS_DEEP_STUBS);
when(b.setName("a name").setAddress("an address")).thenReturn(b);
assert b.setName("a name").setAddress("an address") == b; // this passes

Unfortunately this won't give you a generic way of mocking "all the various builder methods" so that they always return this, see the other answer is you need that.

Radiosurgery answered 14/12, 2011 at 10:5 Comment(3)
Also NB that if you get the deep stub ordering "wrong" and cast the result, it will give you some odd looking messages, possibly like java.lang.ClassCastException: org.mockito.internal.creation.jmock.ClassImposterizer$ClassWithSuperclassToWorkAroundCglibBug$$EnhancerByMockitoWithCGLIB$$851828bd cannot be cast to ...Melee
I'd add that verify does not work when using RETURN_DEEP_STUBSApnea
Alternatively, if using annotations: @Mock(answer = Answers.RETURNS_DEEP_STUBS) Builder bIngleside

© 2022 - 2024 — McMap. All rights reserved.