首页 > 解决方案 > 使用 mockmvc 发出单元测试 RestController 发布请求。它在调试时正确执行请求,但为什么模拟响应为空?

问题描述

我对测试有点陌生,目前在使用MockMvc对 Spring RestController进行单元测试时遇到问题。当我执行发布请求以使用 requestBody添加新用户并且我获得请求已成功执行的 http 状态时,问题就会出现,但是一旦我将响应从 MvcResult 转换为字符串,它就会出现一个空字符串。

我调试了测试并检查了请求是否已执行,并且请求正文已成功获取并已通过所有验证,因此按照这个逻辑,我的想法是问题出在 Mockito 上,但我不太明白为什么。有人可以指点我做错了什么吗?

这是我的实体用户:

@Entity
@Table(name ="users")
public class User {

  @Id
  @GeneratedValue(generator= "UUID")
  @GenericGenerator(name="UUID", strategy = "org.hibernate.id.UUIDGenerator")
  @Column(name = "ID")
  private String id;

  @NotBlank(message = ResponseMessage.ERROR_INVALID_FIRST_NAME_INPUT)
  @Column(name = "first_name")
  private String firstName;

  @NotBlank(message = ResponseMessage.ERROR_INVALID_LAST_NAME_INPUT)
  @Column(name = "last_name")
  private String lastName;

  @NotBlank(message = ResponseMessage.ERROR_INVALID_EMAIL_INPUT)
  @Email(message = ResponseMessage.ERROR_INVALID_EMAIL_INPUT)
  @Column(name = "email")
  private String email;

  @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
  @NotBlank(message = ResponseMessage.ERROR_INVALID_PASSWORD_INPUT)
  @Column(name = "password")
  private String password;

  @Column(name = "phone_number")
  private String phoneNumber;

  @Column(name = "address")
  private String address;

  @Temporal(TemporalType.DATE)
  @Column(name = "registered_on")
  private Date registeredDate;
  // Constructors and setters and getters down here
}

这是休息控制器:


@Validated
@RestController
@RequestMapping(path = "/user")
public class UserController {

  @Autowired
  private UserService userService;

  @PostMapping("/add")
  public Response<Object> addUser(@Valid @RequestBody User user) {
    return this.userService.addUser(user);

  // other methods down here
  }

}

这是我的休息控制器测试:


@WebMvcTest(UserController.class)
class UserControllerTest {

  @Autowired
  private MockMvc mvc;

  @MockBean
  private UserService userService;

  private List<User> users;

  void populateUsers() {
    users = new ArrayList<>();
    users.add(new User("test","test","test@test.com","test"));
    users.add(new User("test1","test1","test1@test.com","test1"));
    users.add(new User("test2","test2","test2@test.com","test2"));
  }

  @BeforeEach
  void setUp() {
    populateUsers();
  }

  @AfterEach
  void clear() {
    users.clear();
  }
     // other tests here. that actually work
 
  @Test
  void addUser() throws Exception {
    // Arrange
    Response<Object> expected = new Response<>(200, ResponseMessage.SUCCESS_SAVED_USER);
    ObjectMapper objectMapper = new ObjectMapper();
    User user = new User("Added","User","a@user.com","itWorks");
    String content = "{" +
            "\"firstName\":\"Added\"," +
            "\"lastName\":\"User\"," +
            "\"email\":\"a@user.com\"," +
            "\"password\":\"itWorks\"" +
            "}";
   Mockito.when(userService.addUser(user)).thenReturn(expected);

    // Act
    String actual = mvc
            .perform(TestHttpRequestBuilderHelper.httpPostRequestBuilder("/user/add", content))
            .andExpect(status().isOk())
            .andReturn()
            .getResponse()
            .getContentAsString();

    // Assert
    assertEquals(objectMapper.writeValueAsString(expected), actual);
  }
  
}

我在构建 MockMvcRequestBulders 时使用了另一个帮助程序类,称为 TestHttpRequestBuilderHelper:


public class TestHttpRequestBuilderHelper {

  private static HttpHeaders generateHttpHeaders() {
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.add("Content-type", "application/json; charset=utf-8");
    httpHeaders.add("Accept","application/json");
    return httpHeaders;
  }
   // other overloaded methods here
 
  public static MockHttpServletRequestBuilder httpPostRequestBuilder(String url, String content) {
    return MockMvcRequestBuilders.post(url).headers(generateHttpHeaders()).content(content);
  }

}

这是日志输出:


MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /user/add
       Parameters = {}
          Headers = [Content-Type:"application/json; charset=utf-8", Accept:"application/json", Content-Length:"81"]
             Body = {"firstName":"Added","lastName":"User","email":"a@user.com","password":"itWorks"}
    Session Attrs = {}

Handler:
             Type = com.bibliotek.library.user.UserController
           Method = com.bibliotek.library.user.UserController#addUser(User)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []


org.opentest4j.AssertionFailedError: 
Expected :{"httpCode":200,"message":"User was added successfully"}
Actual   :
<Click to see difference>


    at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
    at org.junit.jupiter.api.AssertionUtils.failNotEqual(AssertionUtils.java:62)
    at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
    at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
    at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1124)
    at com.bibliotek.library.user.UserControllerTest.addUser(UserControllerTest.java:116)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)


2020-12-07 11:33:43.380  INFO 16288 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

Process finished with exit code -1

所以要成功,它必须是这样的:

exptected: {"httpCode":200,"message":"User was added successfully"} 
actual : {"httpCode":200,"message":"User was added successfully"}

但就我而言,它的回归:

expected: {"httpCode":200,"message":"User was added successfully"}
actual: ""

标签: javaspring-bootunit-testingmockitomockmvc

解决方案


推荐阅读