6

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.

Community
  • 1
  • 1
Bick
  • 17,833
  • 52
  • 146
  • 251
  • Does [this question and answer](http://stackoverflow.com/q/13616547/1426891) cover the case you're looking for? – Jeff Bowman Aug 03 '14 at 02:44
  • 1
    Hi Jeff. Thanks. The solution and the example you liked here is for a mock that returns the callback. I need to create a unittest that tests the async method. mocking the service that I want to test and creating a call back is not what I wanted. – Bick Aug 03 '14 at 09:16
  • thanks man for posting complete code in question, i really needed this as i'm a newly hired Android SDET :) – Hasaan Ali Oct 20 '16 at 13:01

2 Answers2

3

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);
}
ethanfar
  • 3,733
  • 24
  • 43
  • 1
    Thanks. 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. – Bick Aug 03 '14 at 08:46
  • You can use the `timeout()` feature to wait for the thread to start. I've edited my answer to reflect that. See: http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#22 – ethanfar Aug 03 '14 at 08:50
  • Thanks. Well. This works (tried it). but It requires me to depend on time estimation. The CountdownLatch is more complex but requires no estimation. – Bick Aug 03 '14 at 08:57
  • 2
    You'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. – ethanfar Aug 03 '14 at 09:03
2

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));
Jiawei Dai
  • 604
  • 7
  • 6