Writing robust and maintainable Spring Boot applications requires more than just business logic — it demands thorough testing. While unit tests catch simple bugs, advanced systems need a more nuanced testing strategy to ensure reliability, performance, and integration under real conditions.

In this post, you’ll learn advanced testing techniques for Spring Boot applications using test slicing, mocking, integration testing, Testcontainers, and more — all tailored for modern, production-grade Java apps.


Test Pyramid for Spring Boot

Follow the test pyramid to balance speed and confidence:

  • Unit Tests: Fast, isolated, test logic in classes
  • Integration Tests: Validate system components together
  • End-to-End (E2E): Full-stack tests mimicking real users

Spring Boot supports all of these layers through its rich testing ecosystem.


Setting Up Dependencies

Include JUnit 5, Mockito, and Spring Boot test modules:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>

Also include specific Testcontainers modules for databases like PostgreSQL or MySQL as needed.


Layered Testing with @WebMvcTest and @DataJpaTest

Instead of loading the whole context, Spring provides test slices:

@WebMvcTest — for controller layer:

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void testGetUser() throws Exception {
        when(userService.getUser(1L)).thenReturn(new UserDTO("John", "john@example.com"));

        mockMvc.perform(get("/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("John"));
    }
}

@DataJpaTest — for repository layer:

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository repository;

    @Test
    void testSaveUser() {
        User user = new User("Alice", "alice@example.com");
        User saved = repository.save(user);
        assertNotNull(saved.getId());
    }
}

Full Integration Tests with @SpringBootTest

For multi-layer testing, use @SpringBootTest:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class ApplicationIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void healthCheck() {
        ResponseEntity<String> response = restTemplate.getForEntity("/actuator/health", String.class);
        assertEquals(HttpStatus.OK, response.getStatusCode());
    }
}

This loads the full Spring context, ideal for validating configurations and third-party integrations.


Mocking with Mockito and @MockBean

Spring Boot integrates seamlessly with Mockito. Use @MockBean to replace a real bean in the test context.

@MockBean
private NotificationService notificationService;

Mockito will inject the mock automatically into the application context, allowing you to isolate your test subject.

Also use @Captor for argument capture:

@Captor
ArgumentCaptor<String> emailCaptor;

Testcontainers for Realistic Database Testing

Use Testcontainers to spin up real databases like PostgreSQL, MySQL, or Redis in Docker for your tests.

@Testcontainers
@SpringBootTest
class PostgresIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
        .withDatabaseName("testdb")
        .withUsername("user")
        .withPassword("pass");

    @DynamicPropertySource
    static void configProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired
    private UserRepository repository;

    @Test
    void shouldSaveAndRetrieveUser() {
        User saved = repository.save(new User("Bob", "bob@example.com"));
        assertTrue(repository.findById(saved.getId()).isPresent());
    }
}

This ensures your application behaves as expected in production-like environments.


Asserting JSON and Response Bodies

Use MockMvc + jsonPath or AssertJ for expressive assertions:

.andExpect(jsonPath("$.email").value("alice@example.com"))

Or parse response bodies into POJOs:

UserDTO user = objectMapper.readValue(response.getContentAsString(), UserDTO.class);
assertEquals("Alice", user.getName());

Testing Security with @WithMockUser

Spring Security tests are simplified using @WithMockUser:

@Test
@WithMockUser(roles = "ADMIN")
void adminAccessTest() throws Exception {
mockMvc.perform(get("/admin/dashboard"))
.andExpect(status().isOk());
}

Use @WithSecurityContext to create custom user roles or tokens.


Best Practices for Spring Boot Testing

  • Write fast unit tests with mocks and stubs
  • Use slices to test specific layers without full context
  • Apply Testcontainers for real DB testing over H2
  • Leverage @SpringBootTest sparingly — it’s heavier
  • Integrate tests into your CI/CD pipeline
  • Ensure tests are isolated, repeatable, and clean up after execution

Conclusion

Advanced testing in Spring Boot helps you validate behavior, catch bugs early, and build resilient applications. With layered testing, testcontainers, mocking, and security integration, you can ensure your application runs reliably in every environment — from development to production.

Invest in your test architecture today, and your codebase will reward you with long-term stability and confidence.