Testing with spies¶
Mockito spies allows dynamic modification of real objects behavior (configured same as mocks, but, by default, all methods work as in raw bean).
Guicey provides SpiesHook
for overriding guice beans with mockito spies.
Important
Spy creation requires real bean instance and so guicey use AOP to intercept real bean access and redirecting all calls through a dynamically created (on first access) spy object. This means that spies would only work with guice-managed beans.
If you need to spy for a manual instance - use partial mocks
Warning
Spies will not work for HK2 beans
Mockito documentation is written in the Mockito
class javadoc.
Additional docs could be found in mockito wiki
Also, see official mockito refcard
and baeldung guides.
Setup¶
Requires mockito dependency (version may be omitted if dropwizard BOM used):
testImplementation 'org.mockito:mockito-core'
Usage¶
Suppose we have a service:
public static class Service {
public String get(int id) {
return "Hello " + id;
}
}
Spying it:
SpiesHook hook = new SpiesHook();
// real spy could be created ONLY after injector startup
final SpyProxy proxy = hook.spy(Service.class);
// SpyProxy implements provider and so could be also used as:
// Provider<Service> provider = hook.spy(Service.class)
TestSupport.build(App.class)
.hooks(hook)
.runCore(injector -> {
// get spy object for configuration
Service spy = proxy.getSpy();
// IMPORTANT: spies configured in reverse order to avoid accidental method call
doReturn("bar1").when(spy).get(11);
// real instance, injected everywhere in application (AOP proxy)
Service service = injector.getInstance(Service.class);
// stubbed result
Assertions.assertEquals("bar1", s1.get(11));
// real method result (because argument is different)
Assertions.assertEquals("Hello 10", s1.get(10));
})
Here doReturn
refer to Mockito.doReturn
used with static import.
Note
As real guice bean used under the hood, all AOP, applied to the original bean, will work.
Tip
Calling guice proxy injector.getInstance(Service.class).get(11)
and spy object
directly spy.get(11)
is equivalent (because guice returns AOP proxy which redirects
call to the spy)
See other examples in mocks section.
Asserting calls¶
Tip
If you want to use spies to track bean access (verify arguments and response) then try trackers which are better match for this case.
As mocks, spies could be used to assert calls:
// method Service.get(11) called on mock just once
verify(spy, times(1)).get(11);
These assertions would fail if method was called more times or using different arguments.
Method result capture¶
Verifying method return value with spies is a bit clumsy:
public static class ResultCaptor<T> implements Answer {
private T result = null;
public T getResult() {
return result;
}
@Override
public T answer(InvocationOnMock invocationOnMock) throws Throwable {
result = (T) invocationOnMock.callRealMethod();
return result;
}
}
ResultCaptor<String> resultCaptor = new ResultCaptor<>();
// capture actual argument value (just to show how to do it)
ArgumentCaptor<Integer> argumentCaptor = ArgumentCaptor.forClass(Integer.class);
Mockito.doAnswer(resultCaptor).when(spy).get(argumentCaptor.capture());
// call method
Assertions.assertThat(spy.get(11)).isEqualTo("bar");
// result captured
Assertions.assertThat(resultCaptor.getResult()).isEqualTo("bar");
Assertions.assertThat(argumentCaptor.getValue()).isEqualTo(11);
Mockito.verify(spy, Mockito.times(1)).get(11);
Why would you need that? It is often useful when verifying indirect bean call.
For example, if we have SuperService
which internally calls Service
and so
there is no other way to verify service call result correctness other than spying it (or use tracker).
Pre initialization¶
As spy object creation is delayed until application startup, it is impossible to configure spy before application startup (as with mocks). Usually it is not a problem, if target bean is not called during startup.
If you need to modify behavior of spy, used during application startup (e.g. by some Managed
),
then there is a delayed initialization mechanism:
SpiesHook hook = new SpiesHook();
// real spy could be created ONLY after injector startup
final SpyProxy proxy = hook.spy(Service.class)
.withInitializer(service -> doReturn("spied").when(service).get(11));
...
Here, configuration from withInitializer
block would be called just after
spy creation (on first access).
And so any Managed
, calling it during startup would use completely configured spy:
@Singleton
public static class Mng implements Managed {
@Inject
Service service;
@Override
public void start() throws Exception {
// "spied" result
service1.get(11);
}
}
Spies reset¶
If you run multiple tests with the same application, then it makes sense to re-configure spies for each test and so the previous spy state must be reset.
Use hook.resetSpies()
to reset all registered mocks
Accessing spy¶
Spy instance (used to configure methods behavior) could be obtained:
- After application startup:
SpyProxy proxy = hook.spy(Service.class); // after app startup Service spy = proxy.getSpy();
- From hook:
Service spy = hook.getSpy(Service.class)