本文介绍 Spring Boot 2 基于 JUnit 4 的单元测试实现方案。
目录
- 开发环境
- 测试 HTTP 接口
- Mock 调用对象
- 测试套件
- 总结
开发环境
- JDK 8
测试 HTTP 接口
Spring Boot 单元测试覆盖的一个最常见的业务场景是 HTTP 接口测试,以下演示如何基于 Spring Boot 单元测试框架测试常见的 HTTP 接口。
创建 Spring Boot 工程,参考:IntelliJ IDEA 创建 Spring Boot 工程。
修改工程 pom 文件,添加
spring-boot-starter-web
和junit
依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<groupId>tutorial.spring.boot</groupId>
<artifactId>spring-boot-unit-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-unit-test</name>
<description>Spring Boot 单元测试示例</description>
<properties>
<java.version>1.8</java.version>
<project.build.encoding.source>UTF-8</project.build.encoding.source>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.encoding.source}</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
- Spring Boot 工程
src\test\java\tutorial\spring\boot
目录下自动生成了一个单元测试类。
package tutorial.spring.boot;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UnitTestApplicationTests {
@Test
public void contextLoads() {
}
}
- 添加待测试的 Controller 方法,HTTP 接口通常定义在 Controller 层。
package tutorial.spring.boot.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IndexController {
@GetMapping(value = "/")
public String index() {
return "Just for Spring Boot unit test!";
}
}
- 添加单元测试类。
package tutorial.spring.boot.controller;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@RunWith(SpringRunner.class)
@WebMvcTest(IndexController.class)
public class IndexControllerTest {
@Autowired
private MockMvc mvc;
@Test
public void testIndex() throws Exception {
this.mvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("Just for Spring Boot unit test!"));
}
}
单元测试运行结果(略)。
上述示例演示如何测试不带参数的 GET 方法,实际应用中 HTTP 接口都要求请求参数,以下演示如何测试带参数的 POST 方法。
- 修改
IndexController
,添加一个新的POST
接口。
@PostMapping(value = "/commit")
public String commit(String content) {
System.out.printf("Commit content: %s%n", content);
return "Received content: " + content;
}
- 修改
IndexControllerTest
,添加测试新增POST
接口的方法。
@Test
public void testCommit() throws Exception {
this.mvc.perform(MockMvcRequestBuilders.post("/commit"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("Received content: null"));
String content = "[Commit Content]";
this.mvc.perform(MockMvcRequestBuilders.post("/commit").param("content", content))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("Received content: " + content));
}
单元测试运行结果(略)。
除 GET
/ POST
接口外的其它接口 PUT
/ DELETE
/ PATCH
测试方法类似,只需调用 org.springframework.test.web.servlet.request.MockMvcRequestBuilders
的不同方法构建不同类型的 HTTP
请求。
Mock 调用对象
通常,Controller 层职责只包含参数校验、包装返回值或重定向等,具体的业务处理会调用 Service 层对象完成,以下演示如何 Mock Service 层对象实现 Controller 层的独立测试。
- 添加 Service 接口。
package tutorial.spring.boot.service;
public interface IndexService {
String commit(String content);
}
- 添加 Service 接口实现。
package tutorial.spring.boot.service.impl;
import org.springframework.stereotype.Service;
import tutorial.spring.boot.service.IndexService;
@Service
public class IndexServiceImpl implements IndexService {
@Override
public String commit(String content) {
System.out.printf("Param[content] is: %s%n", content);
return "Dealed " + content;
}
}
- 改造 Controller 中方法,调用 Service 方法返回。
package tutorial.spring.boot.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import tutorial.spring.boot.service.IndexService;
@RestController
public class IndexController {
private final IndexService indexService;
public IndexController(IndexService indexService) {
this.indexService = indexService;
}
@GetMapping(value = "/")
public String index() {
return "Just for Spring Boot unit test!";
}
@PostMapping(value = "/commit")
public String commit(String content) {
System.out.printf("Commit content: %s%n", content);
return indexService.commit(content);
}
}
- 修改单元测试方法。
package tutorial.spring.boot.controller;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import tutorial.spring.boot.service.IndexService;
@RunWith(SpringRunner.class)
@WebMvcTest(IndexController.class)
public class IndexControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private IndexService indexService;
@Test
public void testIndex() throws Exception {
this.mvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("Just for Spring Boot unit test!"));
}
@Test
public void testCommit() throws Exception {
String request = "test";
String response = "mock response";
Mockito.when(indexService.commit(request)).thenReturn(response);
this.mvc.perform(MockMvcRequestBuilders.post("/commit").param("content", request))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string(response));
}
}
单元测试运行结果(略)。
- 编写 Service 单元测试类
package tutorial.spring.boot.service;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class IndexServiceTest {
@Autowired
private IndexService indexService;
@Test
public void testNotNull() {
Assert.assertNotNull(indexService);
}
@Test
public void testCommit() {
String content = "test";
Assert.assertEquals("Dealed " + content, indexService.commit(content));
}
}
单元测试运行结果(略)。
测试套件
如果有很多单元测试类,可以放在同一个测试套件中运行。
package tutorial.spring.boot;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import tutorial.spring.boot.controller.IndexControllerTest;
import tutorial.spring.boot.service.IndexServiceTest;
@RunWith(Suite.class)
@Suite.SuiteClasses({IndexControllerTest.class, IndexServiceTest.class})
public class SuiteTests {
}
单元测试运行结果(略)。
总结
有关 Spring Boot 和 Spring MVC 更详细的单元测试,请参考官方文档