This is a short series of how to use Spring in integration testing and unit testing. | ||
---|---|---|
Configurations | Mocking | Testing a REST API |
A custom configuration | Configuration logic |
Let’s continue where we’ve left off – multiple configurations for integration tests.
We use different configurations when we need to inject two different sets of objects. For example a real one and mocked one of the same type for different integration test purposes.
Let’s say we have a REST API that calls some component logic, which then calls the database through a DAO (data access object).
In the first set of test scenarios, I wan’t to mock the database, and test that the logic component works correctly (similar to an isolated unit test, but through the API). In other scenarios I want to make sure the entire flow works, up to the database. So I’ll need two seperate configurations – one that injects a mocked DAO component, and one that injects the real one .
Note that managing configurations takes some work. We usually don’t have a configuration per test class, so that means we create a test configuration that serves multiple integration tests. The configuration classes need to be maintained and kept light, so they will fit every consumer.
Configuration pitfalls
Let’s look at the configuration from last time for the mock.
@Configuration public class MockInjectionConfiguration { @Bean public Student student() { Student mockStudent = Mockito.mock(Student.class); when(mockStudent.getName()).thenReturn("Gil"); when(mockStudent.getSubject()).thenReturn("Star Wars"); return mockStudent; } }
Spring injects any object once on startup by default. That means that integration tests share these mocked instances. That’s an issue we need to understand.
The MockInjectionConfiguration mockStudent that is injected, already has the behavior set for it. When the first integration test runs, Spring injects it as it is written.
But when a second integration test tries to set behavior by using Mockito.when on getName(), it will add the behavior, not override it. And when we’re using Mockito.verify(), oh the laughs we’ll have…
The solution to this is a conventional way of writing tests. A better way to do it to define the configuration like this:
@Configuration public class MockInjectionConfiguration { @Bean public Student student() { Student mockStudent = Mockito.mock(Student.class); return mockStudent; } }
The injected object is a plain basic mock. Let the integration tests define their own behavior. That means that any integration test can assume that it’s starting from scratch.
But assumptions are for fools. We need to make sure the assumption is correct, and that means that we need to reset the mock manually. For example, we can use Mockito.reset() in a setup method:
@Before public void setup() { Mockito.reset(student); }
Without this, mocks can seem to behave erratically. As in, they behave exactly as we tell them, but not how we expect.
But even that maybe too much work for some of us.
If we’re lazy and want to avoid that, Spring Boot can do this for us, if we’re declaring the injection in the test @MockBean instead of @Autowired. With @MockBean we don’t need a @Configuration class to inject the mocks. Spring automatically injects a fresh mock of the object with every test. For further setup for the mock you can either use the @Before method, or the tests themselves.
One more thing to remember: Using beans in the production code makes them easy to use them in integration tests. That ease-of-use comes with the price of speed.
Even if your tests don’t use mocks or injections, the code-under-test may do that. Spring is slow in ramp-up and in run-time. If you write unit tests for the code, make sure it is free of beans, and inject the dependencies manually. “Regular” unit tests (the ones that don’t use Spring for injection) run much more quickly. It also makes sense to locate them differently from the integration tests so they can run separately.
In the final part, we’ll see how we set up a test for a REST API that calls a mock internally.
0 Comments