The Perils of Overusing @MockBean in Spring Boot

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.

Related Post

Leave a Reply

Your email address will not be published. Required fields are marked *

×