Spring Boot Test Slices
Purpose: Guide to using Spring
Bootβs @*Test annotations to test
horizontal slices of the application (controller layer,
data layer, etc.) in isolation.
Table of Contents
- What Are Test Slices?
- Available Slices
- WebMvcTest Pattern
- Other Slice Annotations
- Mocking Strategy
- Examples from StockEase
- When to Use Which
- Related Documentation
What Are Test Slices?
Definition
A test slice loads only the layers needed to test a specific functionality, not the entire application.
Benefits
| Benefit | Impact |
|---|---|
| Faster | Load only web layer (100ms) instead of full app (1-2s) |
| Focused | No database overhead; test HTTP logic in isolation |
| Clearer | Easy to see whatβs being tested (just the controller) |
| Cacheable | Spring can cache contexts across similar slice tests |
Comparison: Full vs.Β Slice
Execution: ~2-3 seconds per test"] F1[Web Layer Controllers] F2[Service Layer] F3[Repository Layer] F4[Database Layer] F5[Security Configuration] F6[All Beans] F1 --> F2 --> F3 --> F4 F5 --> F1 end subgraph Slice["Web Layer Slice @WebMvcTest
Execution: ~0.5-1 second per test"] S1[Web Layer Controllers only] S2[Security Configuration] S3[Relevant Beans] S2 --> S1 S1 --> S3 end Full -.->|Use for| FU[Integration tests
Application bootstrap] Slice -.->|Use for| SU[REST API tests
Controller logic] style Full fill:#ffcdd2 style Slice fill:#c8e6c9 style FU fill:#e3f2fd style SU fill:#e3f2fd
Available Slices
1. @WebMvcTest (Web Layer Slicing)
What it loads: Controller + security
configuration
What it excludes: Services,
repositories, database
When to use: Testing REST endpoints
(80% of your tests)
Auto-configured beans: -
MockMvc β HTTP client simulation - Spring
Security configuration - @Controller,
@RestController beans - Exception
handlers
Not configured: -
@Service beans (must mock) -
@Repository beans (must mock) - Database
connections
Example:
@WebMvcTest(ProductController.class)
public class ProductFetchControllerTest {
@Autowired private MockMvc mockMvc;
@MockitoBean private ProductRepository productRepository;
// MockMvc makes HTTP requests
// ProductRepository is a mock
}2. @DataJpaTest (Repository Layer Slicing)
What it loads: JPA repositories,
Hibernate, embedded database
What it excludes: Web layer,
services
When to use: Testing query methods on
repositories
Auto-configured beans: -
TestEntityManager β Low-level JPA testing -
Embedded database (H2 by default) -
@Repository beans
Not configured: - Controllers - Services - Security
Example:
@DataJpaTest
public class ProductRepositoryTest {
@Autowired private TestEntityManager entityManager;
@Autowired private ProductRepository productRepository;
@Test
void testFindByQuantityLessThan() {
// Test repository queries directly
}
}Note: Not currently used in StockEase (repositories are mocked in controller tests)
3. @SpringBootTest (Full Context)
What it loads: Entire application
context
When to use: Integration tests, context
bootstrap
Execution: Slowest (~2-3 seconds)
Example:
@SpringBootTest
@ActiveProfiles("test")
class StockEaseApplicationTests {
@Test
void contextLoads() {
// All beans are loaded and wired
}
}4. @WebFluxTest (Reactive Web)
Not used in StockEase (synchronous Spring MVC)
5. @RestClientTest (REST Client Slicing)
Not used in StockEase (no outbound HTTP clients)
6. @JsonTest (JSON Serialization)
Not used in StockEase (JSON tested indirectly in MockMvc tests)
WebMvcTest Pattern
Structure
@WebMvcTest(ProductController.class) // Load only this controller
@ExtendWith(MockitoExtension.class) // Enable Mockito
public class ProductFetchControllerTest {
@Autowired
private MockMvc mockMvc; // HTTP client
@MockitoBean
private ProductRepository productRepository; // Mocked dependency
@MockitoBean
private JwtUtil jwtUtil; // Mocked dependency
@BeforeEach
void resetMocks() {
Mockito.reset(productRepository, jwtUtil);
// Setup mock behavior
}
@Test
void testEndpoint() throws Exception {
// Use mockMvc.perform(get(...))
}
}Key Components
1. @WebMvcTest Parameter
Specifies which controller to load:
@WebMvcTest(ProductController.class) // Load ProductController
@WebMvcTest({ProductController.class, AuthController.class}) // Multiple
@WebMvcTest // Auto-detect (use first)2. @MockitoBean
Replaces a Spring bean with a Mockito mock:
@MockitoBean
private ProductRepository productRepository; // Mocked
// In test:
when(productRepository.findAll()).thenReturn(Arrays.asList(...));Difference from @Mock: -
@Mock β Plain Mockito mock (not in Spring
context) - @MockitoBean β Spring-aware mock
(injected into beans)
3. MockMvc
Simulates HTTP requests without starting a real server:
mockMvc.perform(get("/api/products")
.with(user("testUser").roles("USER")))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("Product 1"));4. @BeforeEach
Reset mocks before each test to ensure isolation:
@BeforeEach
void resetMocks() {
Mockito.reset(productRepository, jwtUtil);
Mockito.when(jwtUtil.validateToken(Mockito.anyString()))
.thenReturn(true);
}Mocking Strategy
What to Mock in @WebMvcTest
| Component | Mock? | Why |
|---|---|---|
| Repository | β Yes | Weβre testing controller, not DB queries |
| Service | β Yes | Same reason; test one layer at a time |
| JwtUtil | β Yes | Avoid cryptographic overhead |
| AuthenticationManager | β Yes | Avoid Spring Security internals |
| External API clients | β Yes | Would require network calls |
| Controllers | β No | Weβre testing these! |
| RequestMappings | β No | Weβre testing these! |
How to Mock: @MockitoBean
@WebMvcTest(ProductController.class)
public class ProductCreateControllerTest {
// This bean is MOCKED
@MockitoBean
private ProductRepository productRepository;
// This bean is REAL (loaded by @WebMvcTest)
@Autowired
private MockMvc mockMvc;
@Test
void testCreateProduct() throws Exception {
// Setup mock
Product mockProduct = new Product("Test", 10, 100.0);
mockProduct.setId(1L);
when(productRepository.save(any(Product.class)))
.thenReturn(mockProduct);
// Test controller (real)
mockMvc.perform(post("/api/products")
.contentType(APPLICATION_JSON)
.content("{\"name\": \"Test\", ...}")
.with(csrf())
.with(user("admin").roles("ADMIN")))
// Assert response (controller's HTTP response)
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Test"));
}
}Mock Behavior Pattern
// 1. Import Mockito
import static org.mockito.Mockito.*;
// 2. Setup mock in @BeforeEach
@MockitoBean
private ProductRepository productRepository;
@BeforeEach
void setupMocks() {
// Define what the mock should return
when(productRepository.findAll())
.thenReturn(Arrays.asList(product1, product2));
when(productRepository.findById(1L))
.thenReturn(Optional.of(product1));
when(productRepository.save(any(Product.class)))
.thenReturn(product1);
// Define what should happen with invalid input
when(productRepository.findById(999L))
.thenReturn(Optional.empty());
}
// 3. Use in test
mockMvc.perform(get("/api/products"))
.andExpect(status().isOk());
// Behind the scenes: Controller calls mock repository
// Mock returns the predefined list
// Controller serializes to JSON
// MockMvc captures the responseExamples from StockEase
Example 1: ProductFetchControllerTest.java
@ExtendWith(MockitoExtension.class)
@WebMvcTest(ProductController.class) // Only web layer
public class ProductFetchControllerTest {
@Autowired
private MockMvc mockMvc; // HTTP client
@MockitoBean
private ProductRepository productRepository; // Mocked
@MockitoBean
private JwtUtil jwtUtil; // Mocked
@BeforeEach
void resetMocks() {
Mockito.reset(productRepository, jwtUtil);
// Mock returns true for token validation
Mockito.when(jwtUtil.validateToken(Mockito.anyString()))
.thenReturn(true);
}
@Test
void testGetProductByIdSuccess() throws Exception {
Product product1 = new Product("Product 1", 10, 100.0);
product1.setId(1L);
// Setup mock repository
when(productRepository.findById(1L))
.thenReturn(Optional.of(product1));
// Make HTTP request
mockMvc.perform(get("/api/products/1")
.with(user("testUser").roles("USER")))
// Assert response
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Product 1"));
}
}Example 2: ProductCreateControllerTest.java
@WebMvcTest(ProductController.class)
@Import(TestConfig.class) // Import shared test beans
public class ProductCreateControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private JwtUtil jwtUtil; // From TestConfig (real/mocked)
@MockitoBean
private ProductRepository productRepository; // Mocked here
@Test
void testValidProductCreation() throws Exception {
// Given: Admin is authenticated
Product product1 = new Product("Product 1", 10, 100.0);
product1.setId(1L);
product1.setTotalValue(1000.0);
when(productRepository.save(any(Product.class)))
.thenReturn(product1);
// When: POST request with valid data
mockMvc.perform(post("/api/products")
.contentType(APPLICATION_JSON)
.content("{\"name\": \"Product 1\", \"quantity\": 10, \"price\": 100.0}")
.with(csrf())
.with(user("adminUser").roles("ADMIN")))
// Then: Product is created
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Product 1"))
.andExpect(jsonPath("$.quantity").value(10));
}
}Example 3: AuthControllerTest.java (Unit, Not Slice)
// Note: This is a UNIT TEST, not a slice test
// It doesn't use @WebMvcTest
class AuthControllerTest {
@Mock
private AuthenticationManager authenticationManager;
@Mock
private JwtUtil jwtUtil;
@InjectMocks
private AuthController authController; // Not @WebMvcTest!
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this); // Plain Mockito
}
@Test
void testLoginSuccess() {
// No HTTP layer, just method calls
ResponseEntity<ApiResponse<String>> response =
authController.login(loginRequest);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}When to Use Which
β Use @WebMvcTest When:
- Testing REST endpoint logic (controllers)
- Need to verify HTTP status codes
- Testing request/response serialization
- Need MockMvc for HTTP simulation
Examples in StockEase:
ProductFetchControllerTest β Testing GET /api/products
ProductCreateControllerTest β Testing POST /api/products
ProductUpdateControllerTest β Testing PUT /api/products/{id}
ProductDeleteControllerTest β Testing DELETE /api/products/{id}
β Use @DataJpaTest When:
- Testing repository query methods
- Need to verify database interactions
- Testing JPA entity mappings
Not used in StockEase yet (repositories are mocked)
β Use @SpringBootTest When:
- Testing full application flow (end-to-end)
- Verifying bean wiring across layers
- Testing application bootstrap
Examples in StockEase:
StockEaseApplicationTests β Verify context loads
β Use Plain @Test (Unit) When:
- Testing isolated business logic
- No Spring context needed
- Testing utilities, mappers, validators
Examples in StockEase:
AuthControllerTest β Testing auth logic with plain Mockito
Common Pitfalls
β Pitfall 1: Too Many @WebMvcTest Classes
// β BAD: 3 separate test classes for same controller
@WebMvcTest(ProductController.class)
class ProductGetTest { }
@WebMvcTest(ProductController.class)
class ProductPostTest { }
@WebMvcTest(ProductController.class)
class ProductPutTest { }
// β
BETTER: Organize by operation
// ProductFetchControllerTest β All GET operations
// ProductCreateControllerTest β POST operation
// ProductUpdateControllerTest β PUT operationβ Pitfall 2: Mocking the Controller Youβre Testing
// β BAD: Don't mock the controller!
@WebMvcTest(ProductController.class)
public class ProductTest {
@MockitoBean
private ProductController productController; // WRONG!
}
// β
CORRECT: Mock the dependencies
@WebMvcTest(ProductController.class)
public class ProductTest {
@MockitoBean
private ProductRepository productRepository; // β Mock the dependency
}β Pitfall 3: Not Resetting Mocks
// β BAD: Mocks retain state between tests
@Test
void test1() {
when(repo.findAll()).thenReturn(list1);
}
@Test
void test2() {
// repo.findAll() might still return list1!
}
// β
CORRECT: Reset in @BeforeEach
@BeforeEach
void resetMocks() {
Mockito.reset(productRepository, jwtUtil);
Mockito.when(jwtUtil.validateToken(...)).thenReturn(true);
}β Pitfall 4: Over-Mocking
// β BAD: Mocking everything defeats the purpose
@WebMvcTest(ProductController.class)
public class ProductTest {
@MockitoBean private ProductRepository repo;
@MockitoBean private ProductService service;
@MockitoBean private ProductValidator validator;
// Now we're not testing anything real!
}
// β
BETTER: Mock only external dependencies
@WebMvcTest(ProductController.class)
public class ProductTest {
@MockitoBean private ProductRepository repo; // External dependency
// Services and validators are real (part of controller's concern)
}Related Documentation
Testing Fundamentals
- Testing Strategy β Goals and philosophy
- Test Pyramid β Unit/slice/integration breakdown
- Naming Conventions β Test method naming
Implementation
- Controller Integration Tests β MockMvc details
- Test Data & Fixtures β TestConfig, mock data
- Security Tests β Role-based testing
Main Architecture
- Testing Architecture β Entry point
- Backend Architecture β Controllers being tested
Last Updated: October 31, 2025
Version: 1.0
Status: β
Based on StockEase
implementation