原文: https://javatutorial.net/how-to-perform-unit-testing-for-controllers-and-services

如您所知,测试非常重要。 因此,在本教程中,您将学习如何完成测试!更具体地说,您将学习如何在控制器和服务上执行该测试

如何对控制器和服务执行单元测试 - 图1

控制器

普通控制器执行 2 件事之一:

  • 渲染视图

要么

  • 处理表单提交

让我们看一个代码示例:

  1. @RestController
  2. @RequestMapping("/employee/account/*")
  3. public class EmployeeAccountController {
  4. private EmployeeService employeeService;
  5. // define the logger to which we will be writing stuff for debugging purposes
  6. private static Logger log = LoggerFactory.getLogger(EmployeeAccountController.class);
  7. @Autowired
  8. public EmployeeAccountController(EmployeeService employeeService) {
  9. this.employeeService = employeeService;
  10. }
  11. @PostMapping(value="/register/process", produces="application/json")
  12. public Response registrationProcess(ModelMap model, @RequestBody Employee reqEmployee) {
  13. Employee employee = null;
  14. try {
  15. // try to validate the user using the validate() function defined in the EmployeeService class
  16. employee = employeeService.validate(employee.getEmail(), employee.getUsername(), employee.getPassword());
  17. }
  18. catch (Exception e) {
  19. // if the given information did not match the validate() method criteria then print it out to the console
  20. log.debug(e.getMessage(), e);
  21. }
  22. // return appropriate string message
  23. return employee != null ? new Response("Successful registration.") : new Response("Unsuccessful registration.");
  24. }
  25. }

通过查看上面的代码片段,您将看到这只是一个普通的Controller,它对用户以表单形式给出的信息进行有效性检查。 最后,它会根据有效性检查的响应返回一个字符串,即“成功消息”或“失败消息”。

很棒! 但它缺少一些东西。 那就是单元测试! 让我们添加它。

但是,在向您展示代码之前,我想向您解释什么是MockMvc。 这是一个 Spring MVC 组件,主要用于为控制器组件创建单元测试。 如何使用MockMvc的示例代码片段:

  1. private MockMvc name;
  2. @MockBean
  3. private Service service;
  4. @BeforeEach
  5. public string doSomething() throws Exception {
  6. service = MockMvcBuilders.standaloneSetup(className(service)).build();
  7. }

@MockBean 注释可帮助我们在控制器中模拟依赖项。

EmployeeAccountControllerTest.java

  1. @ExtendWith(SpringExtension.class)
  2. @Tag("Controller")
  3. public class EmployeeAccountControllerTest {
  4. private MockMvc mockMvc;
  5. @MockBean
  6. private EmployeeService employeeService;
  7. // define the logger to which we will be writing stuff for debugging purposes
  8. @BeforeEach
  9. public void test(TestInfo info) throws Exception {
  10. mockMvc = MockMvcBuilders.standaloneSetup(new EmployeeAccountController(employeeService)).build();
  11. }
  12. @Test
  13. @DisplayName("Return some error message text")
  14. public void ReturnErrorMessage() throws Exception {
  15. Employee emp = new Employee();
  16. emp.setEmail("demo@demo.com");
  17. emp.setUsername("demoname");
  18. emp.setPassword("demo123");
  19. Gson gSon = new Gson();
  20. String gsonEmployee = gSon.toJson(emp);
  21. Mockito.when(employeeService.validate("demo@demo.com", "demoname", "demo123")).thenReturn(null);
  22. // the result
  23. MvcResult result = mockMvc.perform(post("/employee/account/register/process").contentType(MediaType.APPLICATION_JSON).content(gsonEmployee)).andExpect(status().isOk()).andReturn();
  24. MockHttpServletResponse response = result.getResponse();
  25. ObjectMapper mapper = new ObjectMapper();
  26. Response responseString = mapper.readValue(response.getContentAsString(), Response.class);
  27. assertTrue(responseString.getCode().equals("Successful registration."));
  28. assertTrue(responseString.getMessage().equals("Please try again!"));
  29. }
  30. }

基于类的名称,您已经知道这是控制器的测试类。

细分

  • @BeforeEach:在每次单元测试之前运行的代码
  • @Test:单元测试本身
  • @DisplayName:JUnit5 注解,用于为单元测试分配描述性文本
  • @Tag:可用于测试发现和执行
  • @ExtendWith(.class):将 Spring 5 测试上下文框架与 JUnit 5 集成

因此,在上面的代码中,首先我们使用@ExtendWith注解,该注解用于将 Spring 5 测试框架与 JUnit 5 集成在一起。然后,我们使用@Tag 注解,用于指定这是 Test类。 然后,我们再次使用@Mockbean注解,它可以帮助我们模拟依赖项。 然后我们使用@BeforeEach注解,该注解表示将在每个单元测试之前在 之前运行的代码。 在我们的案例中,建立员工服务。 然后,我们使用@Test 注解指定以下方法为 Test,然后我们也使用@DisplayName注解,如上所述,该注解为单元测试方法提供了描述性文本(在代码段中不是最具描述性的)。

在该方法中,我们将Employee实例转换为 JSON,然后将 Mock 行为设置为每次调用validate()时都返回null。 然后我们调用控制器方法。

服务

Controller测试示例中,我使用EmployeeService作为服务。 现在在本小节中,我们将看到该服务的实现

EmployeeServiceImpl.java

  1. @Service
  2. public class EmployeeServiceImpl implements EmployeeService {
  3. private EmployeeDAO employeeDAO;
  4. @Autowired
  5. public EmployeeServiceImpl(EmployeeDAO empDAO) {
  6. employeeDAO = empDAO;
  7. }
  8. @Override
  9. public void update(Employee emp) {
  10. employeeDAO.update(emp);
  11. }
  12. // method that checks whether the employee exists or not
  13. public boolean exists(String username) throws Exception {
  14. List<Employee> employees = (List<Employee>) employeeDAO.findByUsername(username);
  15. // check if there are employees matching the given username
  16. if (employees.getSize() != 0) {
  17. return true;
  18. }
  19. // throw exception
  20. throw new Exception("Employee does not exist in database.");
  21. // return false if there are no matches
  22. return false;
  23. }
  24. }

细分

该类的实现非常简单–首先,我们创建一个构造函数,该构造函数接受Employee数据访问对象(DAO),并用于分配给我们的私有成员变量。 然后,我们得到了一个update(Employee)方法,该方法可以执行此操作–根据作为参数提供的信息(Employee)更新雇员记录。 最后,我们获得了exist(String)方法,该方法检查具有指定用户名的员工是否存在。 返回truefalse

现在,我们为实现创建测试类。

EmployeeServiceTest.java

  1. @ExtendWith(SpringExtension.class)
  2. @Tag("Service")
  3. public class EmployeeServiceTest {
  4. @MockBean
  5. private EmployeeDAO employeeDAO;
  6. private EmployeeService employeeService;
  7. // code run before unit tests
  8. @BeforeEach
  9. public void test(TestInfo info) throws Exception {
  10. employeeService = new EmployeeServiceImpl(employeeDAO);
  11. assertTrue(testInfo.getDisplayName().equals("Error message"));
  12. }
  13. @TestInfo
  14. @DisplayName("Error message")
  15. public void throwException_When_EmployeeDoesNotExist() {
  16. String username = "employee123";
  17. Mockito.when(employeeDao.findByUsername(username)).thenReturn(new ArrayList<User>());
  18. assertThatThrownBy(() -> employeeService.exists(username)).isInstanceOf(Exception.class).hasMessage("Employee does not exist in database.");
  19. }
  20. }

细分

我们为EmployeeService创建一个Test类。 在每个单元测试之前,我们运行test(TestInfo)方法,然后运行throwException_When_EmployeeDoesNotExist()方法。