【问题标题】:Java unit testing - how to unit test an async method (uses callback) with mockito answerJava 单元测试 - 如何使用 mockito 答案对异步方法(使用回调)进行单元测试
【发布时间】:2023-03-06 04:34:01
【问题描述】:

我在类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();
  }
}

我通常用

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

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

所以我想一个解决方案是使用CountDownLatch(在other 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));
}

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

【问题讨论】:

  • this question and answer 是否涵盖您正在寻找的案例?
  • 嗨,杰夫。谢谢。您在这里喜欢的解决方案和示例是用于返回回调的模拟。我需要创建一个测试异步方法的单元测试。模拟我想要测试的服务并创建回调不是我想要的。
  • 感谢您发布有问题的完整代码,我真的需要这个,因为我是新雇用的 Android SDET :)

标签: java unit-testing callback mockito


【解决方案1】:

我不明白您为什么需要以 CountDownLatch 开头(根据您发布的代码判断),所以我将在我的回答中跳过它,请随时添加评论来解释我的意思不见了。

我会测试如下:

@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);
}

【讨论】:

  • 谢谢。如果没有一些等待技术,这将无法解决。 verify().response() 在线程仍在休眠时运行。我认为 mockito 有其他选择来解决它。
  • 您可以使用timeout() 功能等待线程启动。我已经编辑了我的答案以反映这一点。见:docs.mockito.googlecode.com/hg/latest/org/mockito/…
  • 谢谢。好。这有效(尝试过)。但这需要我依赖时间估计。 CountdownLatch 更复杂,但不需要估算。
  • 您当然是对的,但我不确定使用模拟是否可以获得相同的效果,这是您最初的问题。如果您愿意避免模拟,那么您的解决方案就可以了。不过需要注意的一件事是,您的解决方案处于单元测试的灰色地带,将其称为集成测试可能更合适。就单元测试而言,您正在运行带有锁的真正多线程测试这一事实有点牵强。不过,这是个人品味和偏好的问题。
【解决方案2】:

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

测试线程/异步代码的更好和更简单的方法是使用类似ConcurrentUnit

final Waiter waiter = new Waiter();

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

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

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

【讨论】:

    【解决方案3】:

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

    我有这个方法可以测试:

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

    它在 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);
    

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

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

    那么我是如何在 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));
    

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

    然后测试并验证:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-15
      • 1970-01-01
      • 2017-06-13
      • 2016-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多