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

I have the following method in Logic class

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 with

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 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 also read that about "Answer in Mokito" might be better for this,
But I could not fully follow the examples. How do I write unit tests for methods using callbacks using Mockito tools?
Thank you.

+3


source to share


3 answers


I didn't understand why you need it CountDownLatch

(judging by the code you provided), so I'll skip that in my answer, feel free to add a comment explaining what I'm missing.

I would test it like this:



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

      

+2


source


The CoundownLatch approach works fine, but if that assertion in your callback fails, the test will still succeed. What for? The main test thread does not know what the other threads are doing - so assertion errors are only noticed if they occur in the main test thread.

A better and easier approach to testing multi-threaded / asynchronous 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 CountdownLatch and waiter-driven assertions as you would expect.

+2


source


I haven't tried CountDownLatch before, but YES, doAnswer in Mockito will do the async method check.

I have this method to test:

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

      

where it calls the async method in the 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 calling callback.done with an element:

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

      

So how I did a 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: when iUserDAO.loginPhoneNumber is called with three arguments (any String, any String, and any Callback.class), it calls the callback.done method to return a Model instance.

Then check it out and check:

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

      

+2


source







All Articles