Thursday, June 9, 2016

Mockito ArgumentCaptor example

Lets observe the following class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class MyService {

    private Collaborator collaborator;

    public MyService(Collaborator collaborator) {
        this.collaborator = collaborator;
    }

    public void doSomething() {
        Thing thing = new Thing();
        thing.setType("ABC");
        collaborator.doStuffWith(thing);
    }
}

If we were to test the method doSomething() what we would be interested in,
perhaps mostly is to make sure that the collaborator that it uses is reached and the appropriate
parameters are passed. We could do that very easily by just verifying on a mock

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class MyServiceTest {

    private Collaborator collaborator = Mockito.mock(Collaborator.class);
    private MyService myService = new MyService(collaborator);


    @Test
    public void shouldDoSomething() throws Exception {
        myService.doSomething();
        verify(collaborator).doStuffWith(any(Thing.class));
    }
}


But there is a peculiar thing about this method. The argument that is passed into the collaborator
function doStuffWith() its using an object. Objects as its well known, contain other objects and/or
primitive variables. Since the object thing its being created internally in the method rather than be
injected(Its a hardwired dependency), we have no way of accurately knowing about it anything else but its type. So if we were curious about knowing more precisely about that object, what we would have to do is to spy on it. Mockito, allows us to spy on the objects that are passed to the mocks using a little tool called Argument Captor.
Let's have a look at how this test would look like if we were spying the object thing.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class MyServiceTest {

    private Collaborator collaborator = mock(Collaborator.class);
    private MyService myService = new MyService(collaborator);


    @Test
    public void shouldUseTheRightThing() throws Exception {
        ArgumentCaptor<Thing> argument = ArgumentCaptor.forClass(Thing.class);

        myService.doSomething();

        verify(collaborator).doStuffWith(argument.capture());
        assertThat(argument.getValue().getType(),is("ABC"));
    }
}

As you see, spying is an interesting way of in a non intrusive manner(without having to refactor), you can discover things about your code.

Now a question comes to our head. But declaring that object of type Thing in the method like that, is not an smell? Well, maybe but have in mind that if you were to inject that object from either the constructor or via a setter injection, we could say that you would be changing the api of the class. So that is not very good, because maybe you don't know if the clients that use the class are actually capable of providing the object thing or if would that even make sense form a design point of view.

To express this last point in a more pragmatical form, I am going to show you another 2 more intrusive refactoring to this class that could help you test that object, but that will need from you to sacrifice in design.  

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class MyService {

    private Collaborator collaborator;
    private ThingBuilder thingBuilder;

    public MyService(Collaborator collaborator, ThingBuilder builder) {
        this.collaborator = collaborator;
        this.thingBuilder = builder;
    }

    public void doSomething() {
        Thing thing = thingBuilder.withType("ABC").build();
        collaborator.doStuffWith(thing);
    }
}
If you had a builder, you could mock it and train it, but you would pay a design price of having to add the builder to the constructor.
Even if you choose to use a setter to set the builder or keeping the original constructor as it is(so you don't affect the clients) and overloading, you are still sacrificing your design for the purpose of the test. Your test would also become more complex. It would look like this:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Test
    public void shouldUseTheRightThing() throws Exception {
        Thing thing = new Thing();
        thing.setType("ABC");
        when(thingBuilder.withType(anyString())).thenReturn(thingBuilder);
        when(thingBuilder.build()).thenReturn(thing);

        myService.doSomething();

        verify(collaborator).doStuffWith(thing);
    }
The other alternative I was thinking about would be to override equals and hashcode, so you could do a comparison in your test against a newly created object that would be the
expectation.

1
2
3
4
5
6
7
8
9
@Test
    public void shouldUseTheRightThing() throws Exception {
        Thing thing = new Thing();
        thing.setType("ABC");

        myService.doSomething();

        verify(collaborator).doStuffWith(thing);
    }

It looks naive, but again is intrusive. You are adding 2 methods in your entity, just for the purpose of making the test green.

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Thing {
    private String type;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Thing thing = (Thing) o;

        return type != null ? type.equals(thing.type) : thing.type == null;

    }

    @Override
    public int hashCode() {
        return type != null ? type.hashCode() : 0;
    }
}

No comments:

Post a Comment

Share with your friends