When it comes to testing Spring Boot applications, the @MockBean
annotation is a powerful tool that can help us isolate the component under test. It replaces the actual bean with a mocked instance, making it easier to control. However, with great power comes great responsibility. Overusing @MockBean
can lead to several issues, some of which we’ll explore in this blog post, along with a more suitable approach.
The Problem with Too Many @MockBeans
Firstly, let’s understand what happens when you use @MockBean
:
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.mockito.Mockito.`when`
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
@SpringBootTest
class SomeServiceTest {
@MockBean
private lateinit var someService: SomeService
@Test
fun `hello world`() {
`when`(someService.hello()).thenReturn("Hello, mocked world!")
assertEquals("Hello, mocked world!", someService.hello())
}
}
In the above example, SomeService
is mocked so that it will return a predefined value when its hello
method is called. This is fine for unit testing of SomeService
class in isolation. However, there are downsides ..
Context Overhead
Each @MockBean
creates a new application context. If multiple tests use different configurations of mocks, the context is reloaded for each configuration, which can eventually slow down your test suite.
Unreliable Integration Tests
One of the main points of integration testing is to test how different parts of the application work together. By introducing too many mocks, you inevitably end up not testing the actual interactions between beans, missing potentially critical integration issues.
Deviating from Reality
This point expands on the previous one about integration tests. Excessive mocking can cause tests to pass in situations where the real application would fail. This false sense of security can conceal configuration issues and runtime behaviour of your beans in production.
Solution? Lean or Real Context
While mocking is useful and sometimes inevitable, here are a few tips that help you find a healthy balance.
The Obvious: Limit the Use of @MockBean
Limit the use of @MockBean
only to the scenarios where it is strictly necessary. Prefer the use of TestContainers instead of mocking when an external system is involved, such as a third-party service or a database:
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.utility.DockerImageName
@SpringBootTest
class SomeServiceIntegrationTests {
companion object {
private val postgreSQLContainer = PostgreSQLContainer(
DockerImageName.parse("postgres:13")
).apply {
withDatabaseName("integration-tests-db")
withUsername("sa")
withPassword("sa")
}
init {
postgreSQLContainer.start()
}
}
// No mocking, use the real service out of the box if possible
@Autowired
private lateinit var helloService: HelloService
// Don't use Spring if it doesn't benefit the test
private lateinit var dbService: DbService
@BeforeEach
fun init() {
// Use the real service with a real database
dbService = DbService(
DbConfig(
url = postgreSQLContainer.jdbcUrl,
user = postgreSQLContainer.username,
password = postgreSQLContainer.password
)
)
}
@Test
fun `test db`() {
// Make calls to dbService and test the real interaction with the database
}
}
Use Specialized Test Annotations
Spring Boot offers more specialized test annotations that focus on testing a particular slice of the application context, reducing the need for mocks:
@DataJpaTest
for repository tests@WebMvcTest
for controller tests@JsonTest
for JSON serialization/deserialization tests
These annotations configure only the necessary parts of the context, which is often faster and less error prone than a @SpringBootTest
with several @MockBean
annotations.
Conclusion
While @MockBean
is useful, excess use can lead to a bloated test suite with less valuable assertions. Instead, aim to use real beans when possible and resort to @MockBean
mainly for external services. This approach tends to lead to more robust tests that better reflect the actual behaviour of your Spring Boot application.
Thanks for reading and happy testing! As usual, source code is available on GitHub.