用Mockito调用回调


70

我有一些代码

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

我如何使用Mockito捕获回调对象,并调用callback.reply(x)

Answers:


79

您想要设置一个Answer可以执行此操作的对象。在https://static.javadoc.io/org.mockito/mockito-core/2.8.47/org/mockito/Mockito.html#answer_stubs上查看Mockito文档。

你可能会写类似

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当然应该替换为原来的样子)


40
顺便说一句,如果该函数返回void,则需要执行doAnswer(...)。when(mockedObject).method(any(Callback []。class)); 如在stackoverflow.com/questions/3581754/中所述
托马斯(Thomas)


54

考虑使用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]为所需数据类型的开销。它还要求您从WITHIN Answer中对系统的预回调状态做出任何断言,这意味着您的Answer可能会越来越大。

而是异步处理回调:使用ArgumentCaptor捕获传递给服务的Callback对象。现在,您可以在测试方法级别进行所有声明,并reply在选择时调用。如果您的服务负责多个同时的回调,则此方法特别有用,因为您可以更好地控制回调返回的顺序。


但是,如果带有回调的方法被测试间接调用(换句话说,它是由测试代码触发的但不在测试代码中触发的)调用,您可以这样做吗?
托马斯

通常,如果您正在单元测试中使用回调,那么您要么传递被测系统的接口以进行调用(可以模拟),要么模拟要接收回调的依赖项方法(因此您必须自己调用回调,如上)。将捕获的回调接口交给另一个要调用的类是非典型的,但是没有理由您不能这样做。这是你的意思吗?
杰夫·鲍曼

您可以通过使用invocation.getArgumentAt(1, Callback.class).reply();
Steve

@Steve不能避免问题,只是将强制转换隐藏在方法调用中。您仍然在运行时断言这arguments[1]是回调,而不是对象,这超出了编译器可以保证的范围。没什么大不了,但这是有区别的。
杰夫·鲍曼

9

如果您有以下方法:

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

您的问题似乎不在此问题的范围内,因此请提出另一个问题
Sumit Kumar Saha

3
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*/);
    }
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.