1. Overview
In this short tutorial, we’ll focus on how to test Callbacks using the popular testing framework Mockito.
We’ll explore two solutions, firstly using an ArgumentCaptor and then the intuitive doAnswer() method.
To learn more about testing well with Mockito, check out our Mockito series here.
2. Introduction to Callbacks
A callback is a piece of code that is passed as an argument to a method, which is expected to call back (execute) the argument at a given time.
This execution may be immediate as in a synchronous callback, but more typically it might happen at a later time as in an asynchronous callback.
A common scenario for the use of Callbacks is during service interactions when we need to process the response from a service call.
In this tutorial, we’ll use the Service interface shown below as the collaborator in test cases:
public interface Service { void doAction(String request, Callback<Response> callback); }
In the Callback argument we pass a class which will handle the response using the reply(T response) method:
public interface Callback<T> { void reply(T response); }
2.1. A Simple Service
We’ll also use a straightforward service example to demonstrate how to pass and invoke the callback:
public void doAction() { service.doAction("our-request", new Callback<Response>() { @Override public void reply(Response response) { handleResponse(response); } }); }
The handleResponse method checks to see if the response is valid before adding some data to the Response object:
private void handleResponse(Response response) { if (response.isValid()) { response.setData(new Data("Successful data response")); } }
For clarity, we’ve opted not to use a Java Lamda expression but the service.doAction call could also be written more concisely:
service.doAction("our-request", response -> handleResponse(response));
To learn more about Lambda expressions have a look here.
3. Using an ArgumentCaptor
Now let’s look at how we use Mockito to grab the Callback object using an ArgumentCaptor:
@Test public void givenServiceWithValidResponse_whenCallbackReceived_thenProcessed() { ActionHandler handler = new ActionHandler(service); handler.doAction(); verify(service).doAction(anyString(), callbackCaptor.capture()); Callback<Response> callback = callbackCaptor.getValue(); Response response = new Response(); callback.reply(response); String expectedMessage = "Successful data response"; Data data = response.getData(); assertEquals( "Should receive a successful message: ", expectedMessage, data.getMessage()); }
In this example, we first create an ActionHandler before calling the doAction method of this handler. This is simply a wrapper to our Simple Service doAction method call which is where we invoke our callback.
Next, we verify that doAction was called on our mock service instance passing anyString() as the first argument and callbackCaptor.capture() as the second, which is where we capture the Callback object. The getValue() method can then be used to return the captured value of the argument.
Now that we’ve got the Callback object, we create a Response object which is valid by default before we call the reply method directly and assert that the response data has the correct value.
4. Using the doAnswer() Method
Now we’ll look at a common solution for stubbing methods that have callbacks using Mockito’s Answer object and doAnswer method to stub the void method doAction:
@Test public void givenServiceWithInvalidResponse_whenCallbackReceived_thenNotProcessed() { Response response = new Response(); response.setIsValid(false); doAnswer((Answer<Void>) invocation -> { Callback<Response> callback = invocation.getArgument(1); callback.reply(response); Data data = response.getData(); assertNull("No data in invalid response: ", data); return null; }).when(service) .doAction(anyString(), any(Callback.class)); ActionHandler handler = new ActionHandler(service); handler.doAction(); }
And, in our second example, we first create an invalid Response object which will be used later in the test.
Next, we set up the Answer on our mock service so that when doAction is called, we intercept the invocation and grab the method arguments using invocation.getArgument(1) to get the Callback argument.
The last step is to create the ActionHandler and call doAction which causes the Answer to be invoked.
To learn more about stubbing void methods have a look here.
3. Conclusion
In this brief article, we covered two different ways to approach testing callbacks when testing with Mockito.
As always, the examples are available in this GitHub project.