Java unit testing - how to unit test an async method (uses callback) with mockito answer
Solution 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);
}
Solution 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.
Solution 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));
Bick
Updated on June 18, 2022Comments
-
Bick almost 2 years
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. -
Bick over 9 yearsThanks. This isn't going to solve it without some wait technic. The verify().response() runs while the thread is still sleeping. I think mockito has some other option to solve it.
-
ethanfar over 9 yearsYou can use the
timeout()
feature to wait for the thread to start. I've edited my answer to reflect that. See: docs.mockito.googlecode.com/hg/latest/org/mockito/… -
Bick over 9 yearsThanks. Well. This works (tried it). but It requires me to depend on time estimation. The CountdownLatch is more complex but requires no estimation.
-
ethanfar over 9 yearsYou're right of course, but I'm not sure it's possible to get the same effect using mocks, which was your original question. If you're willing to avoid mocks, your solution is just fine. One thing to notice though, your solution is in the grey area of unit testing, and it might be more appropriate to refer to it as an integration test. The fact that you're running a real multi-threaded test with locks and all is a bit of a stretch as far as unit testing goes. It's a matter of personal taste and preference though.