Mockito doesn't seem to be able to spy on function calls inside Functional interfaces. Suppose I have a simple Spring Boot App with a service:
@Service
public class TestService {
Function<Integer, Integer> mapping = this::add2;
Integer add2(Integer integer) {
return integer + 2;
}
}
And a test:
@SpyBean
TestService testService;
@Test
public void mockitoTest2(){
doReturn(6).when(testService).add2(2);
System.out.println(testService.mapping.apply(2));
}
The test will return 4 instead of 6. Is this expected, or worth a bug report?
This is expected. Mockito creates a spy by making a shallow copy, and the method reference this::add2
gets copied over while keeping the reference to the old this
.
TestService myTestService = new TestService();
TestService mySpy = Mockito.spy(myTestService);
In this example, mySpy
is an instance of a generated subclass of TestService, which has all of its overridable methods overridden to delegate to Mockito, and all of its instance state shallow-copied from myTestService
. This means that myTestService.mapping == mySpy.mapping
, which also implies that the reference to this
(meaning myTestService
) is captured in the Function is copied over.
Method references applied to an instance capture that instance, as on the Oracle page on Method References under "Kinds of Method References". The object that receives the add2
call is the original, not the spy, so you get the original behavior (4) and not the spy-influenced behavior (6).
This should be somewhat intuitive: You can call the Function<Integer, Integer>
without passing around a TestService instance, so it's pretty reasonable that the Function contains an implicit reference to a TestService implementation. You're seeing this behavior because the spy instance has its state copied from a real instance after the Function is initialized and this
is stored.
Consider this alternative, which you could define on TestService:
BiFunction<TestService, Integer, Integer> mapping2 = TestService::add2;
Here, the function mapping2
doesn't apply to a particular object, but instead applies to any instance of TestService passed in. Consequently, your test would call this:
@Test
public void mockitoTest2(){
doReturn(6).when(testService).add2(2);
System.out.println(testService.mapping2.apply(testService, 2));
}
...and because you are passing in your spy testService
, it will handle the virtual method call to add2
and invoke the behavior set on the spy (returning 6). There is no implicitly-saved this
, so your function works as you'd expect.
See also: Mockito runnable: wanted but not invoked?