Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I test a method call inside an asynchronous operation in unit testing

I have a method that first performs a series of actions, after which it starts an asynchronous task. I want to test this method, but I don’t understand how I can verify that the asynchronous operation has completed.

Using Moсkito, I want to verify that the foo method was executed 2 times, once before the start of the asynchronous task and once inside it. The problem is that at the time of the Mockito check the asynchronous task might not yet call the method inside the asynchronous operation. Therefore, the test is sometimes performed, and sometimes not.

This is the example of my method:

void testingMethod() {
    // some operations
    someObject.foo();
    CompletableFuture.runAsync(() -> {
        // some other operations
        someObject.foo();
    });
}

And example of my test where someObject is mocked:

@Test
public void testingMethodTest() {
    testObject.testingMethod();

    Mockito.verify(someObject, Mockito.times(2)).foo();
}

Is there a way to wait for an asynchronous operation to complete before the verify method. Or is this a bad way to test and what can you advise in this case?

like image 945
Silird Avatar asked Sep 19 '25 04:09

Silird


2 Answers

The problem boils down to to the fact that the tested method calls a static method: CompletableFuture.runAsync(). Static methods in general give little control for mocking and asserting.

Even if you use a sleep() in your test, you cannot assert whether someObject.foo() is called asynchronously or not. If the call is made on the calling thread, the test will still pass. Moreover using sleep() slows down your tests, and a too short sleep() will cause the test to randomly fail.

If really this seems to be the only solution, you should use a library like Awaitability that polls until the assertion is satisfied, with a timeout.

There are several alternatives to make your code easier to test:

  1. Make testingMethod() return a Future (as you thought in the comments): this does not allow asserting the asynchronous execution, but it avoids waiting too much;
  2. Wrap the runAsync() method in another service that you can mock, and capture the argument;
  3. If you are using Spring, move the lambda expression to another service, in a method annotated with @Async. This allows to easily mock and unit test that service, and removes the burden of calling runAsync() directly;
  4. Use a custom executor, and pass it to runAsync();

If you are using Spring, I would recommend using the third solution as it really is the cleanest, and avoids cluttering your code with runAsync() calls everywhere.

Options 2 and 4 are very similar, it just changes what you have to mock.

If you go for the fourth solution, here is how you can do it:

Change tested class to use a custom Executor:

class TestedObject {
    private SomeObject someObject;
    private Executor executor;

    public TestedObject(SomeObject someObject, Executor executor) {
        this.someObject = someObject;
        this.executor = executor;
    }

    void testingMethod() {
        // some operations
        someObject.foo();
        CompletableFuture.runAsync(() -> {
            // some other operations
            someObject.foo();
        }, executor);
    }
}

Implement a custom Executor that simply captures the command instead of running it:

class CapturingExecutor implements Executor {

    private Runnable command;

    @Override
    public void execute(Runnable command) {
        this.command = command;
    }

    public Runnable getCommand() {
        return command;
    }
}

(you could also @Mock the Executor and use an ArgumentCaptor but I think this approach is cleaner)

Use the CapturingExecutor in your test:

@RunWith(MockitoJUnitRunner.class)
public class TestedObjectTest {
    @Mock
    private SomeObject someObject;

    private CapturingExecutor executor;

    private TestedObject testObject;

    @Before
    public void before() {
        executor = new CapturingExecutor();
        testObject = new TestedObject(someObject, executor);
    }

    @Test
    public void testingMethodTest() {
        testObject.testingMethod();

        verify(someObject).foo();
        // make sure that we actually captured some command
        assertNotNull(executor.getCommand());

        // now actually run the command and check that it does what it is expected to do
        executor.getCommand().run();
        // Mockito still counts the previous call, hence the times(2).
        // Not relevant if the lambda actually calls a different method.
        verify(someObject, times(2)).foo();
    }
}
like image 145
Didier L Avatar answered Sep 20 '25 19:09

Didier L


For those you are looking for more suggestions in this topic, please try out Mockito.timeout() function as it is specifically meant to test async methods.

Example:

verify(mockedObject,timeout(100).times(1)).yourMethod();

also there is one more method called after(). It is also useful.

https://www.javadoc.io/doc/org.mockito/mockito-core/2.2.9/org/mockito/verification/VerificationWithTimeout.html

like image 20
ArunSelvam P M Avatar answered Sep 20 '25 19:09

ArunSelvam P M