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()
}
}