Spring Boot RestTemplate POST调用单元测试及控制器状态码疑问
Hey there! Let's walk through your questions step by step, looking at your controller code, test approach, and REST best practices.
Short answer: No, it doesn't meet unit testing standards, and there are critical issues in its implementation.
Problems with your current controller:
- Bug in response logic: You hardcode a
createUserobject but return the inputuserinstead of the created one. Clients expect to receive the fully created resource (with generated ID, etc.), not the exact data they sent. - No separation of concerns: Real-world controllers should delegate business logic to a service layer. Right now, there’s no meaningful logic to test beyond a hardcoded response, making unit testing pointless.
- Incorrect HTTP status code: Spring defaults to 200 OK for this method, but creating a resource should return a 201 Created status (more on that later).
Corrected UserController:
We’ll refactor it to use a service layer (so we can mock the service in tests) and return the proper response:
@RestController @RequestMapping("/users") public class UserController { private final UserService userService; // Constructor injection (ideal for testing with mocks) public UserController(UserService userService) { this.userService = userService; } @PostMapping public ResponseEntity<User> createUser(@RequestBody User user) { User createdUser = userService.createUser(user); // Return 201 Created, plus a Location header pointing to the new resource return ResponseEntity.status(HttpStatus.CREATED) .header(HttpHeaders.LOCATION, "/users/" + createdUser.getId()) .body(createdUser); } }
This way, the controller focuses only on HTTP handling, and business logic lives in UserService—which we can easily mock during unit testing.
Your current test sends a real HTTP request to localhost:8080, which makes it an integration test, not a unit test. Unit tests for controllers should isolate the controller logic, not spin up a server or hit external endpoints.
Correct unit test with MockMvc:
We’ll use Spring’s @WebMvcTest to load only the controller layer, and Mockito to mock the UserService:
import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(UserController.class) public class UserControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; // Converts objects to JSON for requests @MockBean private UserService userService; // Mock the service layer @Test public void testCreateUser_ReturnsCreated() throws Exception { // Test data: input user (no ID, since it's generated by the service) User inputUser = new User(null, "John", 22, List.of("Hi")); // Created user (with generated ID) User createdUser = new User(1, "John", 22, List.of("Hi")); // Tell the mock service to return our created user when called when(userService.createUser(any(User.class))).thenReturn(createdUser); // Simulate the POST request mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(inputUser))) // Verify the status code is 201 Created .andExpect(status().isCreated()) // Verify the Location header points to the new user's URI .andExpect(header().string("Location", "/users/1")) // Verify the response body matches the created user .andExpect(jsonPath("$.id").value(1)) .andExpect(jsonPath("$.name").value("John")) .andExpect(jsonPath("$.age").value(22)) .andExpect(jsonPath("$.messages[0]").value("Hi")); } }
Why this works:
@WebMvcTestloads only the controller and related beans (no full Spring context), making tests fast.@MockBeanreplaces the realUserServicewith a mock, so we can control its behavior without needing a real database or service implementation.MockMvcsimulates HTTP requests without starting a server, so we test the controller in isolation.
According to RESTful best practices:
- 201 Created is the correct status code when you successfully create a new resource. You should also include a
Locationheader with the URI of the newly created resource (as we did in the corrected controller). - 200 OK is typically used for requests that retrieve or update existing resources, not for creating new ones.
Your test correctly asserts 201, but your original controller returns 200 by default—fixing the controller to return ResponseEntity.status(HttpStatus.CREATED) will align it with industry standards.
内容的提问来源于stack exchange,提问作者Sweta Sharma

