Controller Integration Tests
Purpose: Document MockMvc patterns and HTTP testing practices used in StockEase controller tests.
Table of Contents
- MockMvc Fundamentals
- HTTP Request Patterns
- Response Validation
- JSON Path Assertions
- Product CRUD Test Examples
- Parameterized HTTP Tests
- Content Negotiation
- Related Documentation
MockMvc Fundamentals
What is MockMvc?
MockMvc simulates HTTP requests to Spring controllers without starting a real server.
Normal Flow (Integration Test)
request β HTTP Server (port 8080) β Controller β Response
MockMvc Flow (Slice Test)
request β MockMvc β Controller β Response
(No network, no port binding)
Benefits
- Fast: No server startup (< 1 second per test)
- Focused: Test controller logic, not HTTP server
- Isolated: Dependencies are mocked
Setup
@WebMvcTest(ProductController.class)
public class ProductControllerTest {
@Autowired
private MockMvc mockMvc; // Injected by @WebMvcTest
@MockitoBean
private ProductRepository productRepository; // Mocked
// Ready to use mockMvc
}HTTP Request Patterns
GET Request
mockMvc.perform(get("/api/products"))
.andExpect(status().isOk());GET with Path Variable
mockMvc.perform(get("/api/products/1")) // {id} = 1
.andExpect(status().isOk());GET with Query Parameters
mockMvc.perform(get("/api/products?page=0&size=10"))
.andExpect(status().isOk());POST Request with JSON Body
mockMvc.perform(post("/api/products")
.contentType(APPLICATION_JSON)
.content("{\"name\": \"Product\", \"quantity\": 10, \"price\": 100.0}")
.with(csrf())
.with(user("admin").roles("ADMIN")))
.andExpect(status().isOk());PUT Request (Update)
mockMvc.perform(put("/api/products/1")
.contentType(APPLICATION_JSON)
.content("{\"name\": \"Updated\", \"quantity\": 20}")
.with(csrf())
.with(user("admin").roles("ADMIN")))
.andExpect(status().isOk());DELETE Request
mockMvc.perform(delete("/api/products/1")
.with(csrf())
.with(user("admin").roles("ADMIN")))
.andExpect(status().isNoContent());Response Validation
Status Code Assertions
// Successful responses
.andExpect(status().isOk()) // 200
.andExpect(status().isCreated()) // 201
.andExpect(status().isNoContent()) // 204
// Client errors
.andExpect(status().isBadRequest()) // 400
.andExpect(status().isUnauthorized()) // 401
.andExpect(status().isForbidden()) // 403
.andExpect(status().isNotFound()) // 404
// Server errors
.andExpect(status().isInternalServerError()) // 500Content Type Assertions
mockMvc.perform(get("/api/products"))
.andExpect(content().contentType(APPLICATION_JSON));Response Body Assertions
mockMvc.perform(get("/api/products"))
.andExpect(content().json("{...}")); // Match entire JSONJSON Path Assertions
JsonPath Syntax
JsonPath allows querying JSON responses:
$.fieldName β Root-level field
$[0] β First array element
$[*].name β All names in array
$.data.id β Nested field
Examples from StockEase
Assert Single Field
mockMvc.perform(get("/api/products/1"))
.andExpect(jsonPath("$.name").value("Product 1"))
.andExpect(jsonPath("$.quantity").value(10))
.andExpect(jsonPath("$.price").value(100.0));Assert Array Elements
mockMvc.perform(get("/api/products"))
.andExpect(jsonPath("$[0].name").value("Product 1"))
.andExpect(jsonPath("$[1].name").value("Product 2"))
.andExpect(jsonPath("$[*].id", containsInAnyOrder(1L, 2L)));Assert Array Size
mockMvc.perform(get("/api/products"))
.andExpect(jsonPath("$", hasSize(2))); // Array has 2 elementsAssert Field Existence
mockMvc.perform(get("/api/products/1"))
.andExpect(jsonPath("$.id").exists()) // Field exists
.andExpect(jsonPath("$.name").exists());Assert Nested Fields
mockMvc.perform(get("/api/auth/login"))
.andExpect(jsonPath("$.data").isString()) // JWT token
.andExpect(jsonPath("$.message").value("Login successful"));Product CRUD Test Examples
Example 1: GET All Products
Test File:
ProductFetchControllerTest.java
@Test
void testGetAllProducts() throws Exception {
// Given
Product product1 = new Product("Product 1", 10, 100.0);
product1.setId(1L);
Product product2 = new Product("Product 2", 5, 50.0);
product2.setId(2L);
when(productRepository.findAll())
.thenReturn(Arrays.asList(product1, product2));
// When / Then
mockMvc.perform(get("/api/products")
.with(user("testUser")))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].name").value("Product 1"))
.andExpect(jsonPath("$[1].name").value("Product 2"));
}Example 2: POST Create Product (Admin Only)
Test File:
ProductCreateControllerTest.java
@Test
void testCreateProductAsAdmin() throws Exception {
// Given
Product product = new Product("New Product", 10, 100.0);
product.setId(1L);
product.setTotalValue(1000.0);
when(productRepository.save(any(Product.class)))
.thenReturn(product);
// When / Then
mockMvc.perform(post("/api/products")
.contentType(APPLICATION_JSON)
.content("{\"name\": \"New Product\", \"quantity\": 10, \"price\": 100.0}")
.with(csrf())
.with(user("admin").roles("ADMIN")))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1L))
.andExpect(jsonPath("$.name").value("New Product"))
.andExpect(jsonPath("$.quantity").value(10));
}Example 3: PUT Update Product
Test File:
ProductUpdateControllerTest.java
@Test
void testUpdateProduct() throws Exception {
// Given
Product product = new Product("Updated Product", 20, 150.0);
product.setId(1L);
when(productRepository.findById(1L))
.thenReturn(Optional.of(product));
when(productRepository.save(any(Product.class)))
.thenReturn(product);
// When / Then
mockMvc.perform(put("/api/products/1")
.contentType(APPLICATION_JSON)
.content("{\"name\": \"Updated Product\", \"quantity\": 20, \"price\": 150.0}")
.with(csrf())
.with(user("admin").roles("ADMIN")))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Updated Product"))
.andExpect(jsonPath("$.quantity").value(20));
}Example 4: DELETE Product
Test File:
ProductDeleteControllerTest.java
@Test
void testDeleteProduct() throws Exception {
// Given
Product product = new Product("Product to Delete", 10, 100.0);
product.setId(1L);
when(productRepository.findById(1L))
.thenReturn(Optional.of(product));
// When / Then
mockMvc.perform(delete("/api/products/1")
.with(csrf())
.with(user("admin").roles("ADMIN")))
// No content returned (204)
.andExpect(status().isNoContent());
}Example 5: GET Product Not Found
Test File:
ProductFetchControllerTest.java
@Test
void testGetProductByIdNotFound() throws Exception {
// Given
when(productRepository.findById(999L))
.thenReturn(Optional.empty());
// When / Then
mockMvc.perform(get("/api/products/999")
.with(user("testUser")))
.andExpect(status().isNotFound());
}Parameterized HTTP Tests
Why Parameterized?
Test multiple scenarios with one method:
@ParameterizedTest
@CsvSource({
"adminUser, ADMIN, 200", // Admin: allowed
"regularUser, USER, 403" // User: forbidden
})
void testCreateByRole(String user, String role, int expectedStatus)
throws Exception {
mockMvc.perform(post("/api/products")
.with(SecurityMockMvcRequestPostProcessors.user(user).roles(role))
.with(csrf()))
.andExpect(status().is(expectedStatus));
}Example: Testing Multiple Roles
From ProductControllerTest.java:
@ParameterizedTest
@CsvSource({
"adminUser, ADMIN",
"regularUser, USER"
})
void testLowStockProductsWithRoles(String username, String role) throws Exception {
// Setup
Product product1 = new Product("Low Stock", 3, 50.0);
product1.setId(1L);
when(productRepository.findByQuantityLessThan(5))
.thenReturn(Arrays.asList(product1));
// Test: Both roles can fetch
mockMvc.perform(get("/api/products/low-stock")
.with(SecurityMockMvcRequestPostProcessors.user(username).roles(role)))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("Low Stock"));
}Content Negotiation
Setting Content Type
// Request sends JSON
mockMvc.perform(post("/api/products")
.contentType(APPLICATION_JSON)
.content("{...}"))
// Request sends form data
mockMvc.perform(post("/api/products")
.contentType(APPLICATION_FORM_URLENCODED)
.param("name", "Product"))
// Response expects JSON
mockMvc.perform(get("/api/products"))
.andExpect(content().contentType(APPLICATION_JSON))Imports
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;Common HTTP Test Patterns
Pattern: Successful CRUD Flow
// 1. Create
mockMvc.perform(post("/api/products")
.with(csrf())
.with(user("admin").roles("ADMIN"))
.content("{...}"))
.andExpect(status().isOk());
// 2. Read
mockMvc.perform(get("/api/products/1")
.with(user("user")))
.andExpect(status().isOk());
// 3. Update
mockMvc.perform(put("/api/products/1")
.with(csrf())
.with(user("admin").roles("ADMIN"))
.content("{...}"))
.andExpect(status().isOk());
// 4. Delete
mockMvc.perform(delete("/api/products/1")
.with(csrf())
.with(user("admin").roles("ADMIN")))
.andExpect(status().isNoContent());Pattern: Testing Error Cases
// Missing required field
mockMvc.perform(post("/api/products")
.contentType(APPLICATION_JSON)
.content("{\"name\": \"\"}") // Missing quantity, price
.with(csrf())
.with(user("admin").roles("ADMIN")))
.andExpect(status().isBadRequest());
// Invalid data type
mockMvc.perform(put("/api/products/1")
.contentType(APPLICATION_JSON)
.content("{\"quantity\": \"not a number\"}")
.with(csrf())
.with(user("admin").roles("ADMIN")))
.andExpect(status().isBadRequest());
// Not found
mockMvc.perform(get("/api/products/999")
.with(user("testUser")))
.andExpect(status().isNotFound());MockMvc Debugging
Print Request/Response
mockMvc.perform(get("/api/products"))
.andDo(print()) // Print HTTP details
.andExpect(status().isOk());Output:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/products
Parameters = {}
Headers = {Authorization=[Bearer ...]}
MockHttpServletResponse:
Status = 200
Content type = application/json
Body = [{"id":1,"name":"Product 1",...}]
Related Documentation
Testing Fundamentals
- Testing Strategy β HTTP testing scope
- Spring Slices β @WebMvcTest details
- Naming Conventions β Test method names
Security & Authorization
- Security Tests β Role-based HTTP tests
- Test Data & Fixtures β MockMvc setup
Main Architecture
- Testing Architecture β Entry point
- Backend Architecture β Controllers being tested
Last Updated: October 31, 2025
Version: 1.0
Status: β
Based on StockEase
controller test patterns