Testing Callbacks with Mockito – 用Mockito测试回调

最后修改: 2018年 6月 26日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this short tutorial, we’ll focus on how to test Callbacks using the popular testing framework Mockito.

在这个简短的教程中,我们将重点介绍如何使用流行的测试框架Mockito来测试回调

We’ll explore two solutions, firstly using an ArgumentCaptor and then the intuitive doAnswer() method.

我们将探索两种解决方案,首先使用ArgumentCaptor,然后使用直观的doAnswer()方法

To learn more about testing well with Mockito, check out our Mockito series here.

要了解有关使用Mockito进行良好测试的更多信息,请查看我们的Mockito系列这里

2. Introduction to Callbacks

2.回调简介

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:

在本教程中,我们将使用下图所示的Service接口作为测试案例中的合作者。

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:

Callback参数中,我们传递一个类,它将使用reply(T response)方法处理响应。

public interface Callback<T> {
    void reply(T response);
}

2.1. A Simple Service

2.1.一个简单的服务

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:

handleResponse方法在向Response对象添加一些数据之前,会检查响应是否有效。

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:

为了清晰起见,我们选择不使用Java Lamda表达式,但service.doAction 调用也可以写得更简洁

service.doAction("our-request", response -> handleResponse(response));

To learn more about Lambda expressions have a look here.

要了解有关Lambda表达式的更多信息,请看这里

3. Using an ArgumentCaptor

3.使用一个ArgumentCaptor

Now let’s look at how we use Mockito to grab the Callback object using an ArgumentCaptor:

现在让我们来看看我们如何使用Mockito来抓取Callback对象,使用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.

在这个例子中,我们首先创建了一个ActionHandler,然后调用这个处理程序的doAction方法。这只是对我们的简单服务doAction方法调用的一个包装器,我们在这里调用我们的回调。

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.

接下来,我们验证doAction是在我们的模拟服务实例上调用的,传递anyString()作为第一个参数,callbackCaptor.capture() 作为第二个参数,这就是我们捕获Callback对象的地方。然后可以使用getValue()方法来返回参数的捕获值。

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.

现在我们已经有了Callback对象,在我们直接调用reply方法并断言响应数据具有正确的值之前,我们创建一个Response对象,该对象默认为有效。

4. Using the doAnswer() Method

4.使用doAnswer()方法

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:

现在我们来看看使用Mockito的Answer对象和doAnswer方法来存根有回调的方法的常见解决方案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.

而且,在我们的第二个例子中,我们首先创建了一个无效的Response对象,它将在以后的测试中使用。

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

接下来,我们在模拟服务上设置了Answer,这样当doAction被调用时,我们拦截调用并使用invocation.getArgument(1)抓取方法参数以获得Callbackargument

The last step is to create the ActionHandler and call doAction which causes the Answer to be invoked.

最后一步是创建ActionHandler并调用doAction,使Answer被调用。

To learn more about stubbing void methods have a look here.

要了解有关存根无效方法的更多信息,请看这里

3. Conclusion

3.总结

In this brief article, we covered two different ways to approach testing callbacks when testing with Mockito.

在这篇简短的文章中,我们介绍了在用Mockito测试时,有两种不同的方法来测试回调。

As always, the examples are available in this GitHub project.

一如既往,在这个GitHub项目中可以找到这些例子。