Java 单元测试 - 如何使用模拟答案对异步方法(使用回调)进行单元测试

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25099500/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 07:20:00  来源:igfitidea点击:

Java unit testing - how to unit test an async method (uses callback) with mockito answer

javaunit-testingcallbackmockito

提问by Bick

I have the following method in the class Logic

我在类逻辑中有以下方法

public class Logic implements ILogic {

@Override
public void doSomethingInterestingAsync(final int number, 
                                        final ICallback callback){
    new Thread(new Runnable() {
        @Override
        public void run() {
            callback.response(number+1);
        }
    }).start();
  }
}

and I usually call it using

我通常用

ILogic.doSomethingInterestingAsync(1, new ICallback() {
    @Override
    public void response(int number) {
        System.out.println(String.format("response - %s", number));
    }
});

Now I want to unit test it.

现在我想对它进行单元测试。

So I figured one solution is with CountDownLatch(found in otherSO thread)
As Follows:

所以我想一个解决方案是使用CountDownLatch(在其他SO 线程中找到)
如下:

@Test
public void testDoSomethingInterestingAsync_CountDownLatch() throws Exception {
    final CountDownLatch lock = new CountDownLatch(1);

    ILogic ILogic = new Logic();
    final int testNumber = 1;
    ILogic.doSomethingInterestingAsync(testNumber, new ICallback() {
        @Override
        public void response(int number) {
            assertEquals(testNumber + 1, number);
            lock.countDown();
        }
    });
    assertEquals(true, lock.await(10000, TimeUnit.MILLISECONDS));
}

And it works great.
But I have also read that about Answer in Mockito might be a better practice for that,
But I couldn't quite follow the examples.
How do I write unit tests for methods using call backs using Mockito tools?
Thanks.

而且效果很好。
但我也读过关于 Mockito 中的回答可能是更好的做法,
但我不能完全遵循这些例子。
如何使用 Mockito 工具使用回调为方法编写单元测试?
谢谢。

回答by ethanfar

I didn't understand why you need the CountDownLatchto begin with (judging by the code you posted), so I'm going to skip that in my answer, feel free to add a comment explaining what I'm missing.

我不明白为什么你需要CountDownLatch开始(根据你发布的代码判断),所以我将在我的回答中跳过它,随时添加评论来解释我遗漏的内容。

I'd test it as follows:

我会按如下方式测试它:

@Test
public void testDoSomethingInterestingAsyncIncrementsParameter() {
    ILogic logic = new Logic();
    ICallback callback = mock(ICallback.class);

    logic.doSomethingInterestingAsync(SOME_NUMBER, callback);

    verify(callback, timeout(1000)).response(SOME_NUMBER + 1);
}

回答by Jonathan

The CoundownLatch approach works ok, but if that assertion in your callback fails the test will still succeed regardless. Why? The main test thread has no idea what other threads are doing - so assertion failures are only noticed if they occur in the main test thread.

CountdownLatch 方法工作正常,但如果回调中的断言失败,无论如何测试仍然会成功。为什么?主测试线程不知道其他线程在做什么 - 因此只有在主测试线程中发生断言失败时才会注意到它们。

A better and simpler approach to testing threaded/async code is to use something like ConcurrentUnit:

测试线程/异步代码的一种更好、更简单的方法是使用类似ConcurrentUnit 的东西:

final Waiter waiter = new Waiter();

new Thread(() -> {
  doSomeWork();
  waiter.assertTrue(true);
  waiter.resume();
}).start();

// Wait for resume() to be called
waiter.await(1000);

No need to use a CountdownLatch and assertions done through the Waiter work as you'd expect.

无需像您期望的那样使用 CountdownLatch 和通过 Waiter 工作完成的断言。

回答by Jiawei Dai

I've not tried CountDownLatch before, but YES, doAnswer in Mockito would accomplish async method verification.

我之前没有尝试过 CountDownLatch,但是是的,Mockito 中的 doAnswer 将完成异步方法验证。

I have this method to test:

我有这个方法来测试:

// initialization
loginTabPresenter = new LoginTabPresenterImpl(iLoginTabView, iUserDAO);
// method to test
loginTabPresenter.onLoginClicked();

where it calls an async method in iUserDAO interface:

它在 iUserDAO 接口中调用异步方法:

public void loginPhoneNumber(String phoneNumber, String password, final IDAOGetCallback callback)
// async method to test
iUserDAO.loginPhoneNumber(phone, password, callback)
// IDAOGetCallback is callback
callback.done(T item, DAOException e);

This is what my code does when callback.done called with an item:

这是我的代码在使用项目调用 callback.done 时所做的:

//item is an user model, it's set when callback.done
setUserModel(userModel);

So how I've done unit test with doAnswer in Mockito:

那么我是如何在 Mockito 中使用 doAnswer 进行单元测试的:

doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                Object[] objects = invocation.getArguments();
                // Cast 2nd argument to callback
                ((IDAOGetCallback) objects[2]).done(iUserModel, null);
                return null;
            }
        }).when(iUserDAO).loginPhoneNumber(anyString(), anyString(), any(IDAOGetCallback.class));

Explain a little bit: when iUserDAO.loginPhoneNumber is called with 3 arguments (any String, any String and any Callback.class), it invokes callback.done method to return a Model instance.

稍微解释一下:当 iUserDAO.loginPhoneNumber 使用 3 个参数(任何 String、任何 String 和任何 Callback.class)调用时,它会调用 callback.done 方法来返回一个 Model 实例。

Then test it and verify:

然后测试并验证:

loginTabPresenter.onLoginClicked();
verify(iMainView, times(1)).setUserModel(any(IUserModel.class));