原文: 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 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
的示例代码片段:
private MockMvc name;
@MockBean
private Service service;
@BeforeEach
public string doSomething() throws Exception {
service = MockMvcBuilders.standaloneSetup(className(service)).build();
}
@MockBean 注释可帮助我们在控制器中模拟依赖项。
EmployeeAccountControllerTest.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
@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
@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()
方法。