💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 如何对控制器和服务执行单元测试 > 原文: [https://javatutorial.net/how-to-perform-unit-testing-for-controllers-and-services](https://javatutorial.net/how-to-perform-unit-testing-for-controllers-and-services) 如您所知,测试非常重要。 因此,在本教程中,您将学习如何完成测试!更具体地说,您将学习如何在**控制器和服务上执行该测试**。 ![java-featured-image](https://img.kancloud.cn/05/3e/053ee0bb59842d92359246c98f815e0c_780x330.jpg) ## 控制器 普通控制器执行 2 件事之一: * 渲染视图 要么 * 处理表单提交 让我们看一个代码示例: ```java @RestController @RequestMapping("/employee/account/*") public class EmployeeAccountController { private EmployeeService employeeService; // define the logger to which we will be writing stuff for debugging purposes private static Logger log = LoggerFactory.getLogger(EmployeeAccountController.class); @Autowired public EmployeeAccountController(EmployeeService employeeService) { this.employeeService = employeeService; } @PostMapping(value="/register/process", produces="application/json") public Response registrationProcess(ModelMap model, @RequestBody Employee reqEmployee) { Employee employee = null; try { // try to validate the user using the validate() function defined in the EmployeeService class employee = employeeService.validate(employee.getEmail(), employee.getUsername(), employee.getPassword()); } catch (Exception e) { // if the given information did not match the validate() method criteria then print it out to the console log.debug(e.getMessage(), e); } // return appropriate string message return employee != null ? new Response("Successful registration.") : new Response("Unsuccessful registration."); } } ``` 通过查看上面的代码片段,您将看到这只是一个普通的`Controller`,它对用户以表单形式给出的信息进行有效性检查。 最后,它会根据有效性检查的响应返回一个字符串,即“成功消息”或“失败消息”。 很棒! 但它缺少一些东西。 那就是单元测试! 让我们添加它。 但是,在向您展示代码之前,我想向您解释什么是`MockMvc`。 这是一个 Spring MVC 组件,主要用于为控制器组件创建单元测试。 如何使用`MockMvc`的示例代码片段: ```java private MockMvc name; @MockBean private Service service; @BeforeEach public string doSomething() throws Exception { service = MockMvcBuilders.standaloneSetup(className(service)).build(); } ``` **@MockBean** 注释可帮助我们在控制器中模拟依赖项。 ### `EmployeeAccountControllerTest.java` ```java @ExtendWith(SpringExtension.class) @Tag("Controller") public class EmployeeAccountControllerTest { private MockMvc mockMvc; @MockBean private EmployeeService employeeService; // define the logger to which we will be writing stuff for debugging purposes @BeforeEach public void test(TestInfo info) throws Exception { mockMvc = MockMvcBuilders.standaloneSetup(new EmployeeAccountController(employeeService)).build(); } @Test @DisplayName("Return some error message text") public void ReturnErrorMessage() throws Exception { Employee emp = new Employee(); emp.setEmail("demo@demo.com"); emp.setUsername("demoname"); emp.setPassword("demo123"); Gson gSon = new Gson(); String gsonEmployee = gSon.toJson(emp); Mockito.when(employeeService.validate("demo@demo.com", "demoname", "demo123")).thenReturn(null); // the result MvcResult result = mockMvc.perform(post("/employee/account/register/process").contentType(MediaType.APPLICATION_JSON).content(gsonEmployee)).andExpect(status().isOk()).andReturn(); MockHttpServletResponse response = result.getResponse(); ObjectMapper mapper = new ObjectMapper(); Response responseString = mapper.readValue(response.getContentAsString(), Response.class); assertTrue(responseString.getCode().equals("Successful registration.")); assertTrue(responseString.getMessage().equals("Please try again!")); } } ``` 基于类的名称,您已经知道这是控制器的测试类。 **细分** * `@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` ```java @Service public class EmployeeServiceImpl implements EmployeeService { private EmployeeDAO employeeDAO; @Autowired public EmployeeServiceImpl(EmployeeDAO empDAO) { employeeDAO = empDAO; } @Override public void update(Employee emp) { employeeDAO.update(emp); } // method that checks whether the employee exists or not public boolean exists(String username) throws Exception { List<Employee> employees = (List<Employee>) employeeDAO.findByUsername(username); // check if there are employees matching the given username if (employees.getSize() != 0) { return true; } // throw exception throw new Exception("Employee does not exist in database."); // return false if there are no matches return false; } } ``` **细分** 该类的实现非常简单–首先,我们创建一个构造函数,该构造函数接受`Employee`数据访问对象(DAO),并用于分配给我们的私有成员变量。 然后,我们得到了一个`update(Employee)`方法,该方法可以执行此操作–根据作为参数提供的信息(`Employee`)更新雇员记录。 最后,我们获得了`exist(String)`方法,该方法检查具有指定用户名的员工是否存在。 返回`true`或`false`。 现在,我们为实现创建测试类。 ### `EmployeeServiceTest.java` ```java @ExtendWith(SpringExtension.class) @Tag("Service") public class EmployeeServiceTest { @MockBean private EmployeeDAO employeeDAO; private EmployeeService employeeService; // code run before unit tests @BeforeEach public void test(TestInfo info) throws Exception { employeeService = new EmployeeServiceImpl(employeeDAO); assertTrue(testInfo.getDisplayName().equals("Error message")); } @TestInfo @DisplayName("Error message") public void throwException_When_EmployeeDoesNotExist() { String username = "employee123"; Mockito.when(employeeDao.findByUsername(username)).thenReturn(new ArrayList<User>()); assertThatThrownBy(() -> employeeService.exists(username)).isInstanceOf(Exception.class).hasMessage("Employee does not exist in database."); } } ``` **细分** 我们为`EmployeeService`创建一个`Test`类。 在每个单元测试之前,我们运行`test(TestInfo)`方法,然后运行`throwException_When_EmployeeDoesNotExist()`方法。