Spring Boot Slice Testing Best Practices
To create a new Cursor Rule:
- Enter the name as
sprint-boot-slice-testing
- Copy & paste the file content from below
For more information, visit the Project rules.
---
description:
globs:
alwaysApply: false
---
# Spring Boot Slice Testing
Spring Boot slice testing allows you to test specific layers or "slices" of your application in isolation, providing faster and more focused tests than full integration tests. This approach helps maintain test clarity, reduces test execution time, and improves maintainability.
## Implementing These Principles
These guidelines are built upon the following core principles:
- **Layer Isolation**: Test each application layer independently without loading the entire Spring context
- **Focused Testing**: Use appropriate slice annotations to load only the components needed for specific functionality
- **Mock Dependencies**: Mock external dependencies and other layers to achieve true unit testing at the slice level
- **Fast Execution**: Minimize Spring context loading to achieve rapid test feedback cycles
## Table of contents
- Rule 1: Use @WebMvcTest for Web Layer Testing
- Rule 2: Use @JdbcTest for Repository Layer Testing
- Rule 3: Use @JsonTest for JSON Serialization Testing
- Rule 4: Use @MockBean for Mocking Dependencies
- Rule 5: Configure Test Profiles Appropriately
- Rule 6: Use @TestConfiguration for Custom Test Setup
## Rule 1: Use @WebMvcTest for Web Layer Testing
Title: Test Controllers in Isolation with @WebMvcTest
Description: Use @WebMvcTest to test only the web layer (controllers) without loading the full application context. This annotation configures Spring MVC infrastructure and auto-configures MockMvc for testing HTTP requests and responses.
**Good example:**
```java
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void shouldReturnUserWhenValidId() throws Exception {
// Given
User user = new User(1L, "John Doe", "john@example.com");
when(userService.findById(1L)).thenReturn(user);
// When & Then
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John Doe"))
.andExpect(jsonPath("$.email").value("john@example.com"));
}
}
Bad Example:
@SpringBootTest
@AutoConfigureTestDatabase
class UserControllerTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void shouldReturnUser() {
// This loads the entire application context unnecessarily
// and requires database setup for a simple controller test
ResponseEntity<User> response = restTemplate.getForEntity("/api/users/1", User.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}
Rule 2: Use @JdbcTest for Repository Layer Testing
Title: Test JDBC Repositories with @JdbcTest Description: Use @JdbcTest to test Spring Data JDBC repositories in isolation. This annotation configures an in-memory database, auto-configures JdbcTemplate and NamedParameterJdbcTemplate, and loads Spring Data JDBC repositories without loading the full application context.
Good example:
@JdbcTest
class UserRepositoryTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private UserRepository userRepository;
@Test
void shouldFindUserByEmail() {
// Given
User user = new User(null, "John Doe", "john@example.com");
User saved = userRepository.save(user);
// When
Optional<User> found = userRepository.findByEmail("john@example.com");
// Then
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("John Doe");
assertThat(found.get().getEmail()).isEqualTo("john@example.com");
}
@Test
void shouldReturnEmptyWhenUserNotFound() {
// When
Optional<User> found = userRepository.findByEmail("nonexistent@example.com");
// Then
assertThat(found).isEmpty();
}
@Test
void shouldUseJdbcTemplateForCustomQueries() {
// Given
jdbcTemplate.update(
"INSERT INTO users (name, email) VALUES (?, ?)",
"Jane Smith", "jane@example.com"
);
// When
Long count = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM users WHERE email LIKE '%@example.com'",
Long.class
);
// Then
assertThat(count).isEqualTo(1L);
}
}
Bad Example:
@SpringBootTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void shouldFindUserByEmail() {
// This loads the entire application context and all beans
// unnecessarily for a simple repository test
User user = new User(null, "John Doe", "john@example.com");
userRepository.save(user);
Optional<User> found = userRepository.findByEmail("john@example.com");
assertThat(found).isPresent();
}
}
Rule 3: Use @JsonTest for JSON Serialization Testing
Title: Test JSON Serialization/Deserialization with @JsonTest Description: Use @JsonTest to test JSON serialization and deserialization logic in isolation. This annotation auto-configures Jackson ObjectMapper and provides JacksonTester helper for testing JSON operations.
Good example:
@JsonTest
class UserJsonTest {
@Autowired
private JacksonTester<User> json;
@Test
void shouldSerializeUser() throws Exception {
// Given
User user = new User(1L, "John Doe", "john@example.com");
// When & Then
assertThat(json.write(user))
.hasJsonPathNumberValue("$.id", 1)
.hasJsonPathStringValue("$.name", "John Doe")
.hasJsonPathStringValue("$.email", "john@example.com");
}
@Test
void shouldDeserializeUser() throws Exception {
// Given
String content = """
{
"id": 1,
"name": "John Doe",
"email": "john@example.com"
}
""";
// When & Then
assertThat(json.parse(content))
.usingRecursiveComparison()
.isEqualTo(new User(1L, "John Doe", "john@example.com"));
}
}
Bad Example:
@SpringBootTest
class UserJsonTest {
@Autowired
private ObjectMapper objectMapper;
@Test
void shouldSerializeUser() throws Exception {
// Loading full application context just for JSON testing
// is overkill and slow
User user = new User(1L, "John Doe", "john@example.com");
String json = objectMapper.writeValueAsString(user);
assertThat(json).contains("John Doe");
}
}
Rule 4: Use @MockBean for Mocking Dependencies
Title: Mock External Dependencies with @MockBean Description: Use @MockBean to mock Spring beans that are dependencies of the component under test. This replaces the bean in the Spring context with a Mockito mock, allowing you to control its behavior during tests.
Good example:
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private OrderService orderService;
@MockBean
private PaymentService paymentService;
@Test
void shouldCreateOrder() throws Exception {
// Given
CreateOrderRequest request = new CreateOrderRequest("Product A", 2);
Order order = new Order(1L, "Product A", 2, BigDecimal.valueOf(100.00));
when(orderService.createOrder(any(CreateOrderRequest.class))).thenReturn(order);
// When & Then
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"productName": "Product A",
"quantity": 2
}
"""))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.productName").value("Product A"));
}
}
Bad Example:
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired
private MockMvc mockMvc;
// Missing @MockBean - this will cause the test to fail
// because OrderService is not available in the context
@Test
void shouldCreateOrder() throws Exception {
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andExpect(status().isCreated());
// This test will fail due to missing dependencies
}
}
Rule 5: Configure Test Profiles Appropriately
Title: Use Test Profiles for Environment-Specific Configuration Description: Configure specific test profiles to override application properties for testing scenarios. Use @ActiveProfiles to activate test-specific configurations that differ from production settings.
Good example:
@JdbcTest
@ActiveProfiles("test")
class UserRepositoryIntegrationTest {
@Autowired
private UserRepository userRepository;
@Test
void shouldUseTestDatabaseConfiguration() {
// Test will use application-test.yml configuration
// which might specify H2 in-memory database
User user = new User(null, "Test User", "test@example.com");
User saved = userRepository.save(user);
assertThat(saved.getId()).isNotNull();
}
}
Bad Example:
@JdbcTest
class UserRepositoryIntegrationTest {
// No @ActiveProfiles annotation
// This might use production database configuration
// leading to unreliable or slow tests
@Autowired
private UserRepository userRepository;
@Test
void shouldSaveUser() {
User user = new User(null, "Test User", "test@example.com");
userRepository.save(user);
}
}
Rule 6: Use @TestConfiguration for Custom Test Setup
Title: Create Custom Test Configuration with @TestConfiguration Description: Use @TestConfiguration to define test-specific bean configurations that override or supplement the main application configuration during testing.
Good example:
@WebMvcTest(UserController.class)
class UserControllerTest {
@TestConfiguration
static class TestConfig {
@Bean
@Primary
public Clock testClock() {
return Clock.fixed(
Instant.parse("2023-12-01T10:00:00Z"),
ZoneOffset.UTC
);
}
}
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void shouldUseFixedTimeForTesting() throws Exception {
// Test with predictable time for consistent results
mockMvc.perform(get("/api/users/current-time"))
.andExpect(status().isOk())
.andExpect(content().string("2023-12-01T10:00:00Z"));
}
}
Bad Example:
@WebMvcTest(UserController.class)
class UserControllerTest {
// No test configuration for time-dependent tests
// This makes tests unreliable and hard to reproduce
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void shouldReturnCurrentTime() throws Exception {
mockMvc.perform(get("/api/users/current-time"))
.andExpect(status().isOk());
// Cannot assert exact time value due to system clock dependency
}
}
Additional Slice Testing Annotations
@WebFluxTest: For testing Spring WebFlux reactive web applications
@RestClientTest: For testing REST clients and @RestTemplate configurations
@AutoConfigureTestDatabase: For configuring test databases in slice tests
@TestPropertySource: For overriding specific properties in test scenarios
@DataJdbcTest: Alternative to @JdbcTest that focuses specifically on Spring Data JDBC repositories
@Sql: For executing SQL scripts before test execution in JDBC tests
```