in java mockito tests ~ read.

Mocking of callback function via Mockito

I suppose, the first case every developer learn in Mockito is mocking methods with a return value. Construction when().thenReturn() covers 80% of all case in a code I suppose. Next thing what developers learn in Mockito is mocking of void method. doAnswer().when() usually used for this purpose.

But I faced with rare case of using Mockito. Imagine that you have such code:

ArrayList<Entity> list = new ArrayList<Entity>();
someObject.someMethod(p1, p2, new InnerInterface<Entity>() {
        @Override
        public boolean onEntry(Entity entity) 
        {
            //some logic
            list.add(entry);
            return true;
        }
    });

The main problem here is mock call of someMethod and somehow send test entity to callback function onEntry(). Construction doAnswer().when() will also help in this case.

doAnswer(
    new Answer<Void>() {
        @Override
        public Void answer(final InvocationOnMock invocation) throws Throwable {
            InnerInterface<Entity> callback = (InnerInterface<Entity>) invocation.getArguments()[2];
                callback.onEntry(someTestEntry);
                return null;
            }
        }
    ).when(mockObject).someMethod(any(P1Param.class), any(P2Param.class), any(InnerInterface.class));

You just need capture correct parameter via invocation.getArguments()[N] and now you can pass to callback function onEntry your test entities.

Update

In comments, I get a question about testing some call inside the callback function, so I created some test example to show how it can be done. So here it is.

EntityProducer.java

public class EntityProducer {

    public List<integer> generate(int size) {
        List<integer> entities = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            entities.add(new Random().nextInt(100));
        }
        return entities;
    }
}

EntityConsumer.java

public class EntityConsumer {

    public void consume(List<integer> entities, IForEach<integer> iterator){
        for (Integer entity: entities) {
            System.out.println("Apply onEntry for " + entity);
            iterator.onEntry(entity);
        }
    }
}

IForEach.java

public interface IForEach<t>{
    void onEntry(T entry);
}

View.java

public class View {

    public void showOk(Integer entity) {
        System.out.println("Entity " + entity + " is ok.");
    }

    public void showError(Integer entity) {
        System.out.println("Entity " + entity + " has some ERROR!");
    }
}

Runner.java

public class Runner {

    private View view;

    public Runner(View view) {
        this.view = view;
    }

    public void start(EntityProducer producer, EntityConsumer consumer) {

        consumer.consume(producer.generate(15), new IForEach<integer>() {
            @Override
            public void onEntry(Integer entry) {
                if (isChecked(entry)) {
                    view.showOk(entry);
                }else{
                    view.showError(entry);
                }
            }
        });
        System.out.println(producer.generate(15));
    }

    public boolean isChecked(Integer entity) {
        return entity % 2 == 0;
    }
}

Now the test for all these things:

public class TestRunner {

    @Test
    public void testSample() {

        EntityConsumer consumer = mock(EntityConsumer.class);
        EntityProducer producer = mock(EntityProducer.class);
        View testView = mock(View.class);

        List<integer> testArray = Arrays.asList(15);

        when(producer.generate(anyInt())).thenReturn(testArray);

        doAnswer(
            new Answer<void>() {
                @Override
                public Void answer(final InvocationOnMock invocation) throws Throwable {
                    IForEach<integer> callback = (IForEach<integer>) invocation.getArguments()[1];
                    for (Integer entity: testArray) {
                        callback.onEntry(entity);
                    }
                    return null;
                }
            }
        ).when(consumer).consume(any(List.class), any(IForEach.class));

        Runner runner = new Runner(testView);
        runner.start(producer, consumer);

        verify(consumer, times(1)).consume(any(List.class), any(IForEach.class));
        verify(testView).showError(anyInt());
        // verify(testView, times(1)).showOk(anyInt()); //uncomment it to fail test, cause there is no call showOk()
    }
}
comments powered by Disqus