Search code examples
javamockitomatcher

Using Mockito argThat in a loop results in NullPointerException


One of the legacy tests uses Mockito argThat to stub corresponding results in a loop.

It is passing if I decrease the number of iterations to 1 (numberOfItems = 1, i.e. like if there's no loop).

As soon as it gets to the 2nd iteration, it errors out with NPE:

java.lang.NullPointerException: Cannot invoke "getValue()" because "arg" is null

The code is like this:

 @Test
 public void testMy() {
     int numberOfItems = 2;
     List<MyData> myDataList = getTestData(numberOfItems);
     
     for (MyData md : myDataList) {
         when(mockedService.someMethod(
                argThat(arg -> Objects.equals(arg.getValue(), md.getMyValue())))) // NPE here on arg.getValue()
             .thenReturn(getExpectedResult(md.getMyValue()));
     }
     
     List<MyData> resData = testService.methodUnderTest(myDataList);
     
     // assert... resData list has all the expected items and values
 }

The method being tested is similar to the following

public List<MyData> methodUnderTest(List<MyData> myDataList) {
    myDataList.forEach(myData -> {
            // ... some logic ...
        
            myData.setResult(mockedService.someMethod(new MyRequest(myData.getMyValue())));
        }
    });

    return myDataList;
}

But for the error case the method call is not even reached, coz NPE happens earlier in the unit test code.

Originally it was passing being used somewhat like this

when(mockedService.someMethod(
       argThat(hasProperty("value", equalTo(md.getMyValue())))))
    .thenReturn(getExpectedResult(md.getMyValue()));

however, I saw a recommendation to prefer comparing the objects directly, so I tried to re-write the stubbing as suggested, getting rid of those equalTo and hasProperty, bot now got into those NPE error :)

Any tips on what am I doing wrong, please? Thanks!!


Solution

  • When you call when(mockedService.someMethod(argThat(arg -> Objects.equals(arg.getValue(), md.getMyValue())))).thenReturn(getExpectedResult(md.getMyValue())); the first time, the method becomes stubbed. When you call it a second time, the existing stub tries to match the second stubbing call. argThat (and all other matcher methods) return null, which makes the matcher of your existing stub throw.

    Two potential solutions:

    • Change your matcher to argThat(arg -> arg != null && ...) to not match null inputs
    • Don't use the when(mock.call(argThat(...))).thenReturn(result) form – which actually calls the method! –, but switch to doReturn(result).when(mock).call(argThat(...))