javaspringjunitmockitospring-scheduled

JUnit testing an asynchronous method with Mockito


I have implemented an asynchronous method in a Java 1.8 class using Spring Framework (version 5.0.5.RELEASE):

public class ClassToBeTested {
    @Autowired
    private MyComponent myComponent;

    @Async
    public void doStuff(List<MyClass> myObjects) {
        CompletableFuture<MyResponseObject>[] futureList = new CompletableFuture[myObjects.size()];
        int count = 0;

        for (MyClass myObject : myObjects) {
            futureList[count] = myComponent.doOtherStuff(myObject);
            count++;
        }

        // Wait until all doOtherStuff() calls have been completed
        CompletableFuture.allOf(futureList).join();

        ... other stuff ...
    }
}

I'm trying to test the class with JUnit and Mockito. I have set it up as follows, with an aim of mocking the doStuff() method's call to the component:

@MockBean
private MyComponent myComponentAsAMock;

@InjectMocks
@Autowired
private ClassToBeTested classToBeTested;

@Test
public void myTest() throws Exception {
    // Create object to return when myComponent.doOtherStuff() is called.
    CompletableFuture<MyResponseObject> completableFuture = new CompletableFuture<MyResponseObject>();
    ... populate an instance of MyResponseObject ...
    completableFuture.complete(myResponseObject);

    // Return object when myComponent.doOtherStuff() is called.
    Mockito.when(
        myComponentAsAMock.doOtherStuff(ArgumentMatchers.any(MyClass.class)))
        .thenReturn(completableFuture);

    // Test.
    List<MyClass> myObjects = new ArrayList<MyClass>();
    MyClass myObject = new MyClass();
    myObjects.add(myObject);
    classToBeTested.doStuff(myObjects);
}

Whilst the unit test seems to be successful when I run it individually in Eclipse, but doing a Maven build of the whole project I notice NullPointerExceptions are being thrown:

[ThreadExecutor2] .a.i.SimpleAsyncUncaughtExceptionHandler : Unexpected error occurred invoking async method 'public void package.ClassToBeTested.doStuff(java.util.List)'.

java.lang.NullPointerException: null
at java.util.concurrent.CompletableFuture.andTree(CompletableFuture.java:1306) ~[na:1.8.0_131]
at java.util.concurrent.CompletableFuture.allOf(CompletableFuture.java:2225) ~[na:1.8.0_131]
at package.ClassToBeTested.doStuff(ClassToBeTested.java:75) ~[classes/:na]

The error is being raised on this line of ClassToBeTested.java:

CompletableFuture.allOf(completedFutureList).join();

It looks like the exception message is being displayed in the Maven build output after the test has finished (there are other tests being run whose output occur before the error message is being displayed), so I'm guessing it's something to do with the fact that the call to doStuff() is asynchronous.

Any assistance would be appreciated.


Solution

  • The solution was to add in a Mockito verification step with a timeout and a check to ensure that the mocked component's method had been called the appropriate number of times:

        Mockito.verify(myComponentAsAMock, Mockito.timeout(1000).times(1)).doOtherStuff(ArgumentMatchers.any(MyClass.class));