使用Mockito调用回调函数

76

我有一些代码

service.doAction(request, Callback<Response> callback);

我该如何使用Mockito获取回调对象,并调用callback.reply(x)方法?

4个回答

89
你想要设置一个执行这个操作的Answer对象。请查看Mockito文档,位于https://static.javadoc.io/org.mockito/mockito-core/2.8.47/org/mockito/Mockito.html#answer_stubs 你可以编写类似以下内容的代码:
when(mockService.doAction(any(Request.class), any(Callback.class))).thenAnswer(
    new Answer<Object>() {
        Object answer(InvocationOnMock invocation) {
            ((Callback<Response>) invocation.getArguments()[1]).reply(x);
            return null;
        }
});

(当然,将x替换为应该的内容)


42
顺便提一下,如果函数返回void,则需要执行doAnswer(...).when(mockedObject).method(any(Callback[].class));,如https://dev59.com/rnA65IYBdhLWcg3w4C2j中所述。 - Thomas
这里有一个类似的链接:https://testing.googleblog.com/2014/03/whenhow-to-use-mockito-answer.html。 - Matt

59

考虑使用ArgumentCaptor,这样做更接近于“获取回调对象”的需求。

/**
 * Captor for Response callbacks. Populated by MockitoAnnotations.initMocks().
 * You can also use ArgumentCaptor.forClass(Callback.class) but you'd have to
 * cast it due to the type parameter.
 */
@Captor ArgumentCaptor<Callback<Response>> callbackCaptor;

@Test public void testDoAction() {
  // Cause service.doAction to be called

  // Now call callback. ArgumentCaptor.capture() works like a matcher.
  verify(service).doAction(eq(request), callbackCaptor.capture());

  assertTrue(/* some assertion about the state before the callback is called */);

  // Once you're satisfied, trigger the reply on callbackCaptor.getValue().
  callbackCaptor.getValue().reply(x);

  assertTrue(/* some assertion about the state after the callback is called */);
}

当回调需要立即返回(同步)时,“Answer”是一个不错的选择。不过,它会引入创建匿名内部类,并将“invocation.getArguments()[n]”中的元素不安全地强制转换为所需数据类型的开销。此外,你还需要在“Answer”中对系统的前回调状态进行任何断言,这意味着你的“Answer”可能会变得越来越大。

相反地,异步处理你的回调:使用ArgumentCaptor捕获传递给服务的Callback对象。现在,你可以在测试方法级别上进行所有断言,并在选择时调用“reply”。如果你的服务负责多个同时发生的回调,这将非常有用,因为你可以更好地控制回调返回的顺序。


但是如果回调方法是间接被测试调用的(换句话说,它是由测试代码触发但不在测试代码中调用的某些东西调用的),那么你能做到吗? - Thomas
通常,如果您在单元测试中使用回调,则要么传递一个接口供您的系统调用(您可以模拟),要么模拟依赖项的方法以_接收_回调(因此您必须自己调用回调,如上所述)。将捕获的回调接口交给另一个类来调用是不典型的,但您完全可以这样做。这是您的意思吗? - Jeff Bowman
您可以通过使用invocation.getArgumentAt(1, Callback.class).reply();来避免类型安全问题。 - Steve
@Steve 这并没有解决问题,只是在方法调用中隐藏了强制类型转换。你仍然在运行时断言 arguments[1] 是一个 Callback,而不是一个 Object,这比编译器本身能够保证的要多。虽然不是很大的问题,但确实存在差异。 - Jeff Bowman

11

如果你有一个这样的方法:

public void registerListener(final IListener listener) {
    container.registerListener(new IListener() {
        @Override
        public void beforeCompletion() {
        }

        @Override
        public void afterCompletion(boolean succeeded) {
            listener.afterCompletion(succeeded);
        }
    });
}

那么你可以按照以下方式轻松地模拟上述方法:

@Mock private IListener listener;

@Test
public void test_registerListener() {
    target.registerListener(listener);

    ArgumentCaptor<IListener> listenerCaptor =
            ArgumentCaptor.forClass(IListener.class);

    verify(container).registerListener(listenerCaptor.capture());

    listenerCaptor.getValue().afterCompletion(true);

    verify(listener).afterCompletion(true);
}

我希望这可以帮助某些人,因为我花了很多时间找出这个解决方案。


我在进行网络检查时遇到了 nullPointerException。你能否分享任何有关在Android中进行API测试的文章或教程? - akshay bhange

2
when(service.doAction(any(Request.class), any(Callback.class))).thenAnswer(
    new Answer() {
    Object answer(InvocationOnMock invocation) {
        Callback<Response> callback =
                     (Callback<Response>) invocation.getArguments()[1];
        callback.reply(/*response*/);
    }
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接