简体   繁体   中英

Mockito Spying on Class that has an internal method reference

I'm seeing a different in behaviour when spying on a service using the @Spy annotation and having Mockito create the Server verses explicitly calling the constructor.

public class MyService {
    private final Supplier<String> methodBCall;

    public MyService() {
        methodBCall = this::methodB;
    }

    public void methodA() {
        methodBCall.get();
    }

    public String methodB() {
        return "methodB";
    }
}
@RunWith(MockitoJUnitRunner.class)
public class MyTest {

    @Spy
    private MyService myService1;
    @Spy
    private MyService myService2 = new MyService();

    @Test
    public void testSucceeds() {
        myService1.methodA();
        verify(myService1, times(1)).methodA();
        verify(myService1, times(1)).methodB();
    }

    @Test
    public void testFails() {
        myService2.methodA();
        verify(myService2, times(1)).methodA();
        verify(myService2, times(1)).methodB();
    }
}

The failing test fails with

Wanted but not invoked:
myService2.methodB();
-> at com.phemi.services.policy.impl.MyTest.testFails

Why do these two behave differently? What is Mockito doing to initialize myService1 that enables it to spy on methodB?

This is a simplified example, in my case to test my service properly I need to call its constructor with an argument (and so cannot use the @Spy with a default constructor). However, when I do that I cannot properly verify method calls.

The spy on myService2 is only created after the object has been constructed, so having a method call in the constructor is not helpfull as it contains a method reference to the initial object (which is not the spy object).

The difference becomes more evident when you compare the implementation for both cases:

Mockito.spy(Class)

public static <T> T spy(Class<T> classToSpy) {
    return MOCKITO_CORE.mock(classToSpy, withSettings()
            .useConstructor()
            .defaultAnswer(CALLS_REAL_METHODS));
}

Mockito.spy(Object)

public static <T> T spy(T object) {
    return MOCKITO_CORE.mock((Class<T>) object.getClass(), withSettings()
            .spiedInstance(object)
            .defaultAnswer(CALLS_REAL_METHODS));
}

As you can see the first case, based on a class (which is used if no instance for @Spy was created), creates a mock first and uses the constructor on the mocked object.

In the second case the constructor is not considered instead a different instance is created.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM