找到你要的答案

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

Q:java单元测试-如何单元测试异步方法(使用回调)与mockito回答

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 other SO thread)
As Follows:

@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.

我在类逻辑中有以下方法

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

现在我想单元测试它。

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

@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.

answer1: 回答1:

I didn't understand why you need the CountDownLatch to 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.

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

我不明白你为什么需要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);
}
answer2: 回答2:

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.

A better and simpler approach to testing threaded/async code is to use something like 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.

的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和断言通过服务员的工作会。

answer3: 回答3:

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

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:

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:

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

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

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.

Then test it and verify:

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

我从来没有试过countdownlatch之前,但是,doanswer Mockito将完成异步方法的验证。

我有这个方法来测试:

// 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);

所以我怎么做doanswer Mockito单元测试:

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个参数(任何字符串,类的任何字符串和任何回调。),它调用callback.done方法返回一个模型实例。

然后测试并验证:

loginTabPresenter.onLoginClicked();
verify(iMainView, times(1)).setUserModel(any(IUserModel.class));
java  unit-testing  callback  mockito