首页 > 解决方案 > 在 Spring MVC 集成测试中处理我的自定义异常

问题描述

我在控制器类中有以下方法:

@PostMapping("employees")
  @ResponseStatus(HttpStatus.CREATED)
  public Employee addEmployee(@Valid @RequestBody Employee employee) {
    try {
      return employeeRepository.save(employee);
    } catch (DataIntegrityViolationException e) {
      e.printStackTrace();
      Optional<Employee> existingEmployee = employeeRepository.findByTagId(employee.getTagId());
      if (!existingEmployee.isPresent()) {
        //The exception root cause was not due to a unique ID violation then
        throw e;
      }
      throw new DuplicateEntryException(
          "An employee named " + existingEmployee.get().getName() + " already uses RFID tagID " + existingEmployee.get().getTagId());
    }
  }

Employee该类有一个名为的字符串字段,其中tagId有一个@NaturalId注释。(请忽略没有专门的服务层,这是一个小而简单的应用程序)。

这是我的习惯DuplicateEntryException

@ResponseStatus(HttpStatus.CONFLICT)
public class DuplicateEntryException extends RuntimeException {

  public DuplicateEntryException() {
    super();
  }

  public DuplicateEntryException(String message) {
    super(message);
  }

  public DuplicateEntryException(String message, Throwable cause) {
    super(message, cause);
  }

}

感谢这@ResponseStatus(HttpStatus.CONFLICT)条线,当我手动测试该方法时,我得到了带有时间戳、状态、错误、消息和路径字段的默认 Spring Boot REST 消息。

我仍然熟悉 Spring 中的测试,并且我有这个测试:

@Test
  public void _02addEmployee_whenDuplicateTagId_thenExceptionIsReturned() throws Exception {
    Employee sampleEmployee = new Employee("tagId01", "John Doe");
    System.out.println("Employees in the database: " + repository.findAll().size()); //prints 1
    // @formatter:off
    mvc.perform(post("/employees").contentType(MediaType.APPLICATION_JSON).content(JsonUtil.toJson(sampleEmployee)))
       .andExpect(status().isConflict())
       .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
       .andExpect(jsonPath("$.message").value("An employee named John Doe already uses RFID tagID tagId01"));
    // @formatter:on

    int employeeCount = repository.findAll().size();
    Assert.assertEquals(1, employeeCount);
  }

您可以猜到,首先运行另一个测试,称为_01addEmployee_whenValidInput_thenCreateEmployee(),它插入一个具有相同 tagID 的员工,这在测试#2 中使用。测试 #1 通过,但测试 #2 没有通过,因为 HTTP 响应如下所示:

MockHttpServletResponse:
           Status = 409
    Error message = null
          Headers = {}
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

在上述响应之前的控制台中,我看到了这个:

Resolved Exception:
             Type = ai.aitia.rfid_employee.exception.DuplicateEntryException

所以我的第二次测试失败了,因为java.lang.AssertionError: Content type not set. 与手动测试相比,是什么导致了不同的行为?这个怎么不退呢?

{
    "timestamp": "2019-01-03T09:47:33.371+0000",
    "status": 409,
    "error": "Conflict",
    "message": "An employee named John Doe already uses RFID tagID tagId01",
    "path": "/employees"
}

更新:我在不同的 REST 端点上也遇到了同样的事情,测试用例导致了我自己的ResourceNotFoundException,但是 MockMvc 对象没有接收到实际的 JSON 错误对象。

Update2:这是我对测试类的类级别注释:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = RfidEmployeeApplication.class)
@AutoConfigureMockMvc
@AutoConfigureTestDatabase
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@TestPropertySource(locations = "classpath:application-test.properties")

标签: spring-bootspring-mvcspring-testjunit5spring-test-mvc

解决方案


org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error 填写错误响应正文的完整信息,但MockMvc它不起作用。我刚刚检查过你可以在这种情况下轻松使用TestRestTemplate

首先只是@Autowired private TestRestTemplate testRestTemplate;在测试课上。

并修改您的测试方法,例如:

ResponseEntity<String> response = testRestTemplate.postForEntity("/employees", sampleEmployee, String.class);
        String message = com.jayway.jsonpath.JsonPath.read(response.getBody(), "$.message");
        String expectedMessage = "An employee named John Doe already uses RFID tagID tagId01";
        Assert.assertEquals(expectedMessage, message);
        Assert.assertTrue(response.getStatusCode().is4xxClientError());
例如。

推荐阅读