# 如何对控制器和服务执行单元测试

> 原文： [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](img/e0db051dedc1179e7424b6d998a6a772.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()`方法。