如何在 Spring Boot 中测试 DELETE、PUT、GET 单个对象请求?

How to test DELETE, PUT, GET single object requests in Spring Boot?

我是 Spring Boot 的新手,我正在尝试了解测试的概念。 最近我试图为我的控制器实现 DELETE 请求测试,但我 运行 遇到了一些问题,我有一些疑问:

有我的测试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();
    }
}

回答您的问题:

  1. 使用 @WebMvcTest 时,您正在对控制器层执行测试,而没有加载应用程序的其余部分(通常称为 Slice 测试 )。这意味着除了 JSON 的序列化和反序列化之外,您在这里要测试的只是您的 Controller 中的逻辑。这意味着你实际上并没有删除任何东西,因为你只是在测试你的控制器层。要实际测试数据库中某些内容的删除,您需要进行成熟的 Integration Test.
  2. 如果没有模拟的 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());
    }
}