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

13,299

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));
Share:
13,299
Bick
Author by

Bick

Updated on June 18, 2022

Comments

  • Bick
    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
    Bick over 9 years
    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.
  • ethanfar
    ethanfar over 9 years
    You 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
    Bick over 9 years
    Thanks. Well. This works (tried it). but It requires me to depend on time estimation. The CountdownLatch is more complex but requires no estimation.
  • ethanfar
    ethanfar over 9 years
    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.