如何在 Spring Boot 中测试 DELETE、PUT、GET 单个对象请求?
How to test DELETE, PUT, GET single object requests in Spring Boot?
我是 Spring Boot 的新手,我正在尝试了解测试的概念。
最近我试图为我的控制器实现 DELETE 请求测试,但我 运行 遇到了一些问题,我有一些疑问:
首先,如果没有要删除的对象,如何测试删除、PUT 或 GET 单个对象请求?我应该在每次测试开始时启动一些对象吗?
其次,在这种测试中注入Service有什么意义?我们在嘲笑它,仅此而已。我自己没有在测试中发现任何服务使用情况。也许有办法用它来解决第一个问题?
有我的测试class
package com.example.springrestuser;
import com.example.springrestuser.user.controller.UserController;
import com.example.springrestuser.user.entity.User;
import com.example.springrestuser.user.security.Sha256;
import com.example.springrestuser.user.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
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.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.time.LocalDate;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ExtendWith(SpringExtension.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private UserService userService;
@Test
public void canAddUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user@gmail.com")
.build();
mvc.perform(MockMvcRequestBuilders.post("/api/users")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated());
}
@Test
public void canDeleteUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user@gmail.com")
.build();
userService.add(user); //maybe it would work somehow?
mvc.perform(MockMvcRequestBuilders.delete("/api/users/{login}","user"))
.andExpect(status().isAccepted());
}
@Test
public void canPutUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user@gmail.com")
.build();
//sending the post method and then put?
mvc.perform(MockMvcRequestBuilders.post("/api/users")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(user)));
user.setName("new_User");
mvc.perform(MockMvcRequestBuilders.put("/api/users/{login}","user"))
.andExpect(status().isAccepted());
}
@Test
public void canGetSingleUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user@gmail.com")
.build();
mvc.perform(MockMvcRequestBuilders.get("/api/users/{login}", "adam"))
.andExpect(status().isOk());
}
}
有控制器class
package com.example.springrestuser.user.controller;
import com.example.springrestuser.user.dto.CreateUserRequest;
import com.example.springrestuser.user.dto.GetUserResponse;
import com.example.springrestuser.user.dto.GetUsersResponse;
import com.example.springrestuser.user.dto.UpdateUserRequest;
import com.example.springrestuser.user.entity.User;
import com.example.springrestuser.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("api/users")
public class UserController {
UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("")
public ResponseEntity<GetUsersResponse> getUsers() {
List<User> all = userService.findAll();
GetUsersResponse getUsersResponse = GetUsersResponse.entityToDtoMapper(all);
return ResponseEntity.ok(getUsersResponse);
}
@GetMapping("{login}")
public ResponseEntity<GetUserResponse> getUser(@PathVariable("login") String login) {
return userService.find(login)
.map(user -> ResponseEntity.ok(GetUserResponse.entityToDtoMapper(user)))
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping("")
public ResponseEntity<Void> createUser(@RequestBody CreateUserRequest request) {
User user = CreateUserRequest.dtoToEntityMapper(request);
userService.add(user);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@PutMapping("{login}")
public ResponseEntity<Void> updateUser(@RequestBody UpdateUserRequest request,
@PathVariable("login") String login) {
Optional<User> user = userService.find(login);
if (user.isEmpty()) {
return ResponseEntity.notFound().build();
}
UpdateUserRequest.dtoToEntityMapper(request, user.get());
userService.update(user.get());
return ResponseEntity.accepted().build();
}
@DeleteMapping("{login}")
public ResponseEntity<Void> deleteUser(@PathVariable("login") String login) {
Optional<User> user = userService.find(login);
if (user.isEmpty()) {
return ResponseEntity.notFound().build();
}
userService.delete(user.get());
return ResponseEntity.accepted().build();
}
}
回答您的问题:
- 使用
@WebMvcTest
时,您正在对控制器层执行测试,而没有加载应用程序的其余部分(通常称为 Slice 测试 )。这意味着除了 JSON 的序列化和反序列化之外,您在这里要测试的只是您的 Controller 中的逻辑。这意味着你实际上并没有删除任何东西,因为你只是在测试你的控制器层。要实际测试数据库中某些内容的删除,您需要进行成熟的 Integration Test.
- 如果没有模拟的
UserService
bean,您的 UserController
将由 Spring 实例化。在这个@WebMvcTest
中你应该做的是根据你要在Controller中测试的逻辑来模拟UserService
。例如,在您的 @DeleteMapping("{login}")
端点中,您需要进行以下测试:
- 测试 #1:将
userService.find(login)
模拟为 return 一个空的 Optional<User>
,然后检查 HTTP 状态代码是否为 404。
- 测试 #2:将
userService.find(login)
模拟为 return 非空 Optional<User>
,然后检查 HTTP 状态代码是否为 202。
大致如下:
@ExtendWith(SpringExtension.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private UserService userService;
@Test
public void shouldDeleteUserIfFound() throws Exception {
// Arrange
String login = "user";
User user = User.builder()
.login(login)
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user@gmail.com")
.build();
doReturn(Optional.of(user)).when(this.userService).find(login);
// Act & Assert
mvc.perform(MockMvcRequestBuilders.delete("/api/users/{login}", login))
.andExpect(status().isAccepted());
}
@Test
public void shouldNotDeleteUserIfNotFound() throws Exception {
// Arrange
String login = "user";
doReturn(Optional.empty()).when(this.userService).find(login);
// Act & Assert
mvc.perform(MockMvcRequestBuilders.delete("/api/users/{login}", login))
.andExpect(status().isNotFound());
}
}
我是 Spring Boot 的新手,我正在尝试了解测试的概念。 最近我试图为我的控制器实现 DELETE 请求测试,但我 运行 遇到了一些问题,我有一些疑问:
首先,如果没有要删除的对象,如何测试删除、PUT 或 GET 单个对象请求?我应该在每次测试开始时启动一些对象吗?
其次,在这种测试中注入Service有什么意义?我们在嘲笑它,仅此而已。我自己没有在测试中发现任何服务使用情况。也许有办法用它来解决第一个问题?
有我的测试class
package com.example.springrestuser;
import com.example.springrestuser.user.controller.UserController;
import com.example.springrestuser.user.entity.User;
import com.example.springrestuser.user.security.Sha256;
import com.example.springrestuser.user.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
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.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.time.LocalDate;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ExtendWith(SpringExtension.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private UserService userService;
@Test
public void canAddUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user@gmail.com")
.build();
mvc.perform(MockMvcRequestBuilders.post("/api/users")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated());
}
@Test
public void canDeleteUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user@gmail.com")
.build();
userService.add(user); //maybe it would work somehow?
mvc.perform(MockMvcRequestBuilders.delete("/api/users/{login}","user"))
.andExpect(status().isAccepted());
}
@Test
public void canPutUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user@gmail.com")
.build();
//sending the post method and then put?
mvc.perform(MockMvcRequestBuilders.post("/api/users")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(user)));
user.setName("new_User");
mvc.perform(MockMvcRequestBuilders.put("/api/users/{login}","user"))
.andExpect(status().isAccepted());
}
@Test
public void canGetSingleUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user@gmail.com")
.build();
mvc.perform(MockMvcRequestBuilders.get("/api/users/{login}", "adam"))
.andExpect(status().isOk());
}
}
有控制器class
package com.example.springrestuser.user.controller;
import com.example.springrestuser.user.dto.CreateUserRequest;
import com.example.springrestuser.user.dto.GetUserResponse;
import com.example.springrestuser.user.dto.GetUsersResponse;
import com.example.springrestuser.user.dto.UpdateUserRequest;
import com.example.springrestuser.user.entity.User;
import com.example.springrestuser.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("api/users")
public class UserController {
UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("")
public ResponseEntity<GetUsersResponse> getUsers() {
List<User> all = userService.findAll();
GetUsersResponse getUsersResponse = GetUsersResponse.entityToDtoMapper(all);
return ResponseEntity.ok(getUsersResponse);
}
@GetMapping("{login}")
public ResponseEntity<GetUserResponse> getUser(@PathVariable("login") String login) {
return userService.find(login)
.map(user -> ResponseEntity.ok(GetUserResponse.entityToDtoMapper(user)))
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping("")
public ResponseEntity<Void> createUser(@RequestBody CreateUserRequest request) {
User user = CreateUserRequest.dtoToEntityMapper(request);
userService.add(user);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@PutMapping("{login}")
public ResponseEntity<Void> updateUser(@RequestBody UpdateUserRequest request,
@PathVariable("login") String login) {
Optional<User> user = userService.find(login);
if (user.isEmpty()) {
return ResponseEntity.notFound().build();
}
UpdateUserRequest.dtoToEntityMapper(request, user.get());
userService.update(user.get());
return ResponseEntity.accepted().build();
}
@DeleteMapping("{login}")
public ResponseEntity<Void> deleteUser(@PathVariable("login") String login) {
Optional<User> user = userService.find(login);
if (user.isEmpty()) {
return ResponseEntity.notFound().build();
}
userService.delete(user.get());
return ResponseEntity.accepted().build();
}
}
回答您的问题:
- 使用
@WebMvcTest
时,您正在对控制器层执行测试,而没有加载应用程序的其余部分(通常称为 Slice 测试 )。这意味着除了 JSON 的序列化和反序列化之外,您在这里要测试的只是您的 Controller 中的逻辑。这意味着你实际上并没有删除任何东西,因为你只是在测试你的控制器层。要实际测试数据库中某些内容的删除,您需要进行成熟的 Integration Test. - 如果没有模拟的
UserService
bean,您的UserController
将由 Spring 实例化。在这个@WebMvcTest
中你应该做的是根据你要在Controller中测试的逻辑来模拟UserService
。例如,在您的@DeleteMapping("{login}")
端点中,您需要进行以下测试:
- 测试 #1:将
userService.find(login)
模拟为 return 一个空的Optional<User>
,然后检查 HTTP 状态代码是否为 404。 - 测试 #2:将
userService.find(login)
模拟为 return 非空Optional<User>
,然后检查 HTTP 状态代码是否为 202。
大致如下:
@ExtendWith(SpringExtension.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private UserService userService;
@Test
public void shouldDeleteUserIfFound() throws Exception {
// Arrange
String login = "user";
User user = User.builder()
.login(login)
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("user@gmail.com")
.build();
doReturn(Optional.of(user)).when(this.userService).find(login);
// Act & Assert
mvc.perform(MockMvcRequestBuilders.delete("/api/users/{login}", login))
.andExpect(status().isAccepted());
}
@Test
public void shouldNotDeleteUserIfNotFound() throws Exception {
// Arrange
String login = "user";
doReturn(Optional.empty()).when(this.userService).find(login);
// Act & Assert
mvc.perform(MockMvcRequestBuilders.delete("/api/users/{login}", login))
.andExpect(status().isNotFound());
}
}