JUnit testing an asynchronous method with Mockito

13,335

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

GarlicBread

Updated on June 21, 2022

Comments

  • GarlicBread
    GarlicBread almost 2 years

    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.