原文: https://javatutorial.net/how-to-perform-unit-testing-for-controllers-and-services
如您所知,测试非常重要。 因此,在本教程中,您将学习如何完成测试!更具体地说,您将学习如何在控制器和服务上执行该测试。

控制器
普通控制器执行 2 件事之一:
- 渲染视图
要么
- 处理表单提交
让我们看一个代码示例:
@RestController@RequestMapping("/employee/account/*")public class EmployeeAccountController {private EmployeeService employeeService;// define the logger to which we will be writing stuff for debugging purposesprivate static Logger log = LoggerFactory.getLogger(EmployeeAccountController.class);@Autowiredpublic 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 classemployee = 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 consolelog.debug(e.getMessage(), e);}// return appropriate string messagereturn employee != null ? new Response("Successful registration.") : new Response("Unsuccessful registration.");}}
通过查看上面的代码片段,您将看到这只是一个普通的Controller,它对用户以表单形式给出的信息进行有效性检查。 最后,它会根据有效性检查的响应返回一个字符串,即“成功消息”或“失败消息”。
很棒! 但它缺少一些东西。 那就是单元测试! 让我们添加它。
但是,在向您展示代码之前,我想向您解释什么是MockMvc。 这是一个 Spring MVC 组件,主要用于为控制器组件创建单元测试。 如何使用MockMvc的示例代码片段:
private MockMvc name;@MockBeanprivate Service service;@BeforeEachpublic string doSomething() throws Exception {service = MockMvcBuilders.standaloneSetup(className(service)).build();}
@MockBean 注释可帮助我们在控制器中模拟依赖项。
EmployeeAccountControllerTest.java
@ExtendWith(SpringExtension.class)@Tag("Controller")public class EmployeeAccountControllerTest {private MockMvc mockMvc;@MockBeanprivate EmployeeService employeeService;// define the logger to which we will be writing stuff for debugging purposes@BeforeEachpublic 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 resultMvcResult 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
@Servicepublic class EmployeeServiceImpl implements EmployeeService {private EmployeeDAO employeeDAO;@Autowiredpublic EmployeeServiceImpl(EmployeeDAO empDAO) {employeeDAO = empDAO;}@Overridepublic void update(Employee emp) {employeeDAO.update(emp);}// method that checks whether the employee exists or notpublic boolean exists(String username) throws Exception {List<Employee> employees = (List<Employee>) employeeDAO.findByUsername(username);// check if there are employees matching the given usernameif (employees.getSize() != 0) {return true;}// throw exceptionthrow new Exception("Employee does not exist in database.");// return false if there are no matchesreturn false;}}
细分
该类的实现非常简单–首先,我们创建一个构造函数,该构造函数接受Employee数据访问对象(DAO),并用于分配给我们的私有成员变量。 然后,我们得到了一个update(Employee)方法,该方法可以执行此操作–根据作为参数提供的信息(Employee)更新雇员记录。 最后,我们获得了exist(String)方法,该方法检查具有指定用户名的员工是否存在。 返回true或false。
现在,我们为实现创建测试类。
EmployeeServiceTest.java
@ExtendWith(SpringExtension.class)@Tag("Service")public class EmployeeServiceTest {@MockBeanprivate EmployeeDAO employeeDAO;private EmployeeService employeeService;// code run before unit tests@BeforeEachpublic 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()方法。
