MockMVC测试遇AssertionError:预期状态码201实际404,求修复方案
Hey there! Let's break down this MockMVC assertion error (expected 201, got 404) step by step—this is a super common issue, so we can fix it quickly, plus I'll share some tips to make your MockMVC tests cleaner.
First off, that error spells out the core problem: your test request isn't matching any controller method in your Spring context. Instead of hitting the endpoint that should return a 201 (resource created), it's hitting a dead end and getting a 404 (resource not found). The most common reasons are:
- Mismatched request path: The URL in your
perform(...)call doesn't match the path in your controller's@PostMapping/@GetMapping/etc. annotation. For example, if your controller uses/api/usersbut your test calls/users, or you misspelled a path variable like/{userId}vs/{id}. - Wrong HTTP method: Your controller uses
@PostMappingbut your test usesget(...), or vice versa. Spring won't match a GET request to a POST endpoint. - Controller not loaded in test context: If you're using
@WebMvcTestand didn't specify the controller class to scan, or your controller lives in a package outside Spring's default scan range, MockMVC can't find it. - Missing/invalid request parameters/body: If your controller method requires a
@RequestParam,@PathVariable, or@RequestBodythat your test doesn't provide (or provides incorrectly), Spring might fail to match the method and return 404 instead of a more obvious error like 400.
Let's go through troubleshooting steps in order of likelihood:
Double-check your request path and HTTP method
Compare your controller code and test code side-by-side. For example, if your controller looks like this:@PostMapping("/api/v1/users") public ResponseEntity<User> createUser(@RequestBody User user) { User savedUser = userService.save(user); return ResponseEntity.status(HttpStatus.CREATED).body(savedUser); }Your test must use the exact same path and POST method:
mockMvc.perform(post("/api/v1/users") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(testUser))) .andExpect(status().isCreated());Even a tiny typo (like
/api/v1/userinstead of/users) will cause a 404.Verify your test context configuration
If you're using@WebMvcTest, explicitly specify the controller you want to test—this ensures Spring only loads that controller's context, avoiding missing beans or misconfiguration:@WebMvcTest(UserController.class) class UserControllerTest { // Test code here }If you're using
@SpringBootTest+@AutoConfigureMockMvc, make sure your controller is in the same package (or subpackage) as your Spring Boot main application class—this is where Spring scans for components by default.Ensure you're passing required parameters/body
- For path variables: Use the placeholder syntax to pass values correctly, like
get("/api/v1/users/{id}", 1)instead of hardcoding incorrectly. - For request bodies: Always set
contentType(MediaType.APPLICATION_JSON)and pass a valid JSON string for@RequestBodymethods. If the request body can't be parsed, Spring might fail to match the controller method.
- For path variables: Use the placeholder syntax to pass values correctly, like
Enable debug logging to see what's happening
Add this to yourapplication-test.propertiesto get detailed Spring MVC request matching logs:logging.level.org.springframework.web=DEBUGThe logs will show exactly which endpoints Spring tried to match your request against, so you can spot why it didn't find the right one.
Writing MockMVC tests doesn't have to be repetitive—here are my go-to tips:
Extract setup code to
@BeforeEach
Move repeated setup (like initializing test objects, MockMvc, or ObjectMapper) to a@BeforeEachmethod to keep your test methods clean:@WebMvcTest(UserController.class) class UserControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; private User testUser; @BeforeEach void setUp() { testUser = User.builder() .name("Martine") .email("martine@example.com") .build(); } }Use static imports for cleaner code
Static import the MockMVC builders and matchers to avoid long class names:import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; // Now your test code is much cleaner: mockMvc.perform(post("/api/v1/users") .contentType(APPLICATION_JSON) .content(objectMapper.writeValueAsString(testUser))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.name").value(testUser.getName()));Mock dependencies with
@MockBean
When testing controllers, you don't need real services or repositories—use@MockBeanto mock their behavior. This makes tests faster, isolated, and easier to control:@WebMvcTest(UserController.class) class UserControllerTest { @MockBean private UserService userService; @Test void createUser_returns201() throws Exception { // Mock the service to return our test user when(userService.save(any(User.class))).thenReturn(testUser); // Run the test mockMvc.perform(post("/api/v1/users") .contentType(APPLICATION_JSON) .content(objectMapper.writeValueAsString(testUser))) .andExpect(status().isCreated()); } }Chain assertions for comprehensive tests
You can verify multiple things in one test call—like status code, response body fields, and headers—instead of writing separate tests for each:mockMvc.perform(post("/api/v1/users") .contentType(APPLICATION_JSON) .content(objectMapper.writeValueAsString(testUser))) .andExpect(status().isCreated()) .andExpect(header().string("Location", "/api/v1/users/1")) .andExpect(jsonPath("$.email").value(testUser.getEmail()));
内容的提问来源于stack exchange,提问作者martine

