Testing with mocks¶
Mockito mocks are essentially an automatic stubs: with the ability to dynamically declare method behavior (by default, all mock methods return default value: often null).
Mocks declared with a @MockBean
annotation.
Warning
Stubs 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¶
Remember
- Do not mock types you don’t own
- Don’t mock value objects
- Don’t mock everything
- Show love with your tests!
For example, suppose we have a service:
public class Service {
public String foo() {
...
}
}
where method foo implements some complex logic, not required in test.
To override service with a mock:
@TestGuiceyApp(App.class)
public class Test {
// register mock (mock would be created automatically using Mockito.mock(Service.class)
@MockBean
Service mock;
// injecting here to show that mock replaced real service
@Inject
Service service;
@BeforeEach
public void setUp() {
// declaring behaviour
when(mock.foo()).thenReturn("static value");
}
@Test
public void test() {
// mock instance instead of service
Assertions.assertEquals(mock, service);
// method overridden
Assertions.assertEquals("static value", service.foo());
}
}
Here when
refer to Mockito.when()
used with static import.
Important
Guice AOP would not be applied to mocks (only guice-managed beans support AOP)
You can also provide a pre-created mock instance (useful if mock used during application startup or partial mocks):
@MockBean
static Service mock = createMock();
Note
When mock is registered with instance, mock field must be static for per-test application run
(default annotation). It may not be static for per-method application startup (with @RegisterExtension
).
Mocking examples¶
Mocking answers for different arguments:
when(mock.foo(10)).thenReturn(100);
when(mock.foo(20)).thenReturn(200);
when(mock.foo(30)).thenReturn(300);
Different method answers (for consequent calls):
when(mock.foo(anyInt())).thenReturn(10, 20, 30);
Using actual argument in mock:
when(mock.getValue(anyInt())).thenAnswer(invocation -> {
int argument = (int) invocation.getArguments()[0];
int result;
switch (argument) {
case 10:
result = 100;
break;
case 20:
result = 200;
break;
case 30:
result = 300;
break;
default:
result = 0;
}
return result;
});
Asserting calls¶
Mock could also be used for calls verification:
// method Service.foo() called on mock just once
verify(mock, times(1)).foo();
// method Service.bar(12) called just once (with exact argument value)
verify(mock, times(1)).bar(12);
These assertions would fail if method was called more times or using different arguments.
Mock reset¶
Mocks are re-set automatically after each test method (and that's why it makes sense to declare mock behavior in test setup method - execured before each test method).
Note
Mock could be reset manually at any time with Mockito.reset(mock)
Mocks automatic reset could be disabled with autoReset
option:
@MockBean(autoReset = false)
Service mock;
Partial mocks¶
If mock is applied for a class with implemented methods, these methods would still be overridden with fake implementations. If you want to preserve this logic, then use spies:
public class AbstractService implements IService {
public abstract String bar();
public String foo() {
return "value";
}
}
@TestGuiceyApp(App.class)
public class Test {
@MockBean
static IService mock = Mockito.spy(AbstractService.class);
@Inject
IService service;
@Test
public void test() {
// default mock implementation for abstract method
Assertions.assertNull(service.bar());
// implemented method preserved
Assertions.assertEquals("value", service.foo());
}
}
Note
The spies section covers only spies, spying on real guice bean instance. Using spies for partial mocks is more related to pure mocking and so it's described here.
Mocks report¶
Mockito provides a mock usage report (Mockito.mockingDetails(value).printInvocations()
),
which could be enabled with @MockBean(printSummary = true)
(report shown after each test method):
\\\------------------------------------------------------------/ test instance = 6d420cdd /
@MockBean stats on [After each] for MockSummaryTest$Test1#test():
[Mockito] Interactions of: Mock for Service, hashCode: 1340267778
1. service.foo(1);
-> at ru.vyarus.dropwizard.guice.test.jupiter.setup.mock.MockSummaryTest$Test1.test(MockSummaryTest.java:55)
- stubbed -> at ru.vyarus.dropwizard.guice.test.jupiter.setup.mock.MockSummaryTest$Test1.setUp(MockSummaryTest.java:50)
Debug¶
When extension debug is active:
@TestGucieyApp(value = App.class, debug = true)
public class Test
All recognized mock fields would be logged:
Applied mocks (@MockBean) on MockSimpleTest:
#mock2 Service2 (r.v.d.g.t.j.s.m.MockSimpleTest) AUTO
#mock1 Service1 (r.v.d.g.t.j.s.m.MockSimpleTest) AUTO
Mocking OpenAPI client¶
If you use some external API with a client, generated from openapi (swagger) declaration, then you should be using it in code like this:
@Inject
SomeApi api;
public void foo() {
Some response = api.someGetCall(...)
}
Where SomeApi
is a generated client class.
Usually, the simplest way is to record real service response (using swagger UI or other generated documentation) or simply enabling client debug in the application (so all requests and responses would be logged).
Store such responses as json files in test resources: e.g. src/test/resources/responses/someGet.json
Now mocking SomeApi
and configure it to return object, mapped from json file content, instead of the real call:
@TestGuiceyApp(App.class)
public class Test {
@MockBean
SomeApi mock;
// injecting here to show that mock replaced real service
@Inject
SomeService service;
@Inject
Environment environment;
@BeforeEach
public void setUp() throws Exception {
// usually better than new ObjectMapper() (already pre-configured with extensions)
ObjectMapper mapper = environment.getObjectMapper();
when(mock.someGetCall(...)).thenReturn(mapper.readValue(
new File("src/test/resources/responses/someGet.json"), Some.class));
}
@Test
public void test() {
// call some service using api internally (mock removes external call)
service.doSomething();
}
}
With it, object, mapped from json file, would be returned on service call, instead of the real api.
Note
In the example, direct file access used instead of classpath lookup because
IDEA by default does not copy .json
resources (it must be additionally configured) and
so direct file access is more universal.