1. 概述
集成测试在应用开发周期中扮演着关键角色,它通过验证系统的端到端行为来确保各组件协同工作。
本教程将介绍如何利用 Spring MVC 测试框架 编写和运行集成测试,在不显式启动 Servlet 容器的情况下测试控制器层。
2. 准备工作
运行本文的集成测试需要以下 Maven 依赖。首先需要最新版本的 junit-jupiter-engine
、junit-jupiter-api
和 spring-test
:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.13</version>
<scope>test</scope>
</dependency>
3. Spring MVC 测试配置
3.1 在 JUnit 5 中启用 Spring
JUnit 5 通过扩展接口支持与测试框架集成。通过 @ExtendWith
注解指定扩展类即可启用:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { ApplicationConfig.class })
@WebAppConfiguration
public class GreetControllerIntegrationTest {
....
}
关键点:
@ContextConfiguration
加载配置类(如ApplicationConfig.class
)- 也可使用 XML 配置:
@ContextConfiguration(locations={"classpath:config.xml"})
@WebAppConfiguration
加载 Web 应用上下文(默认路径src/main/webapp
)
3.2 WebApplicationContext 对象
WebApplicationContext
提供了 Web 应用配置,自动加载所有 Bean 和控制器:
@Autowired
private WebApplicationContext webApplicationContext;
3.3 模拟 Web 上下文 Bean
MockMvc
封装了所有 Web 应用 Bean,使其可用于测试:
private MockMvc mockMvc;
@BeforeEach
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
}
⚠️ 在 @BeforeEach
中初始化,避免每个测试重复代码。
3.4 验证测试配置
验证上下文加载是否正确:
@Test
public void givenWac_whenServletContext_thenItProvidesGreetController() {
ServletContext servletContext = webApplicationContext.getServletContext();
assertNotNull(servletContext);
assertTrue(servletContext instanceof MockServletContext);
assertNotNull(webApplicationContext.getBean("greetController"));
}
✅ 检查点:
ServletContext
非空- 是
MockServletContext
实例 greetController
Bean 存在
4. 编写集成测试
4.1 验证视图名称
测试接口 /homePage
返回正确视图:
@Test
public void givenHomePageURI_whenMockMVC_thenReturnsIndexJSPViewName() {
this.mockMvc.perform(get("/homePage")).andDo(print())
.andExpect(view().name("index"));
}
关键方法解析:
perform()
:发起 GET 请求andDo(print())
:打印请求/响应(调试神器)andExpect()
:验证视图名为 "index"
4.2 验证响应体
测试接口 /greet
返回 JSON 格式数据:
@Test
public void givenGreetURI_whenMockMVC_thenVerifyResponse() {
MvcResult mvcResult = this.mockMvc.perform(get("/greet"))
.andDo(print()).andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Hello World!!!"))
.andReturn();
assertEquals("application/json;charset=UTF-8", mvcResult.getResponse().getContentType());
}
验证点:
- HTTP 状态 200
- JSON 中
message
字段值 - 响应 Content-Type
4.3 发送带路径变量的 GET 请求
测试接口 /greetWithPathVariable/{name}
:
@Test
public void givenGreetURIWithPathVariable_whenMockMVC_thenResponseOK() {
this.mockMvc
.perform(get("/greetWithPathVariable/{name}", "John"))
.andDo(print()).andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(jsonPath("$.message").value("Hello World John!!!"));
}
✅ 路径变量通过方法参数直接传递,可读性强。
4.4 发送带查询参数的 GET 请求
测试接口 /greetWithQueryVariable?name={name}
:
@Test
public void givenGreetURIWithQueryParameter_whenMockMVC_thenResponseOK() {
this.mockMvc.perform(get("/greetWithQueryVariable")
.param("name", "John Doe")).andDo(print()).andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(jsonPath("$.message").value("Hello World John Doe!!!"));
}
两种传参方式:
// 方式1:使用 param()
get("/greetWithQueryVariable").param("name", "John Doe")
// 方式2:URI 模板
get("/greetWithQueryVariable?name={name}", "John Doe")
4.5 发送 POST 请求
测试接口 /greetWithPost
:
@Test
public void givenGreetURIWithPost_whenMockMVC_thenVerifyResponse() {
this.mockMvc.perform(post("/greetWithPost")).andDo(print())
.andExpect(status().isOk()).andExpect(content()
.contentType("application/json;charset=UTF-8"))
.andExpect(jsonPath("$.message").value("Hello World!!!"));
}
表单数据示例:
this.mockMvc.perform(post("/greetWithPostAndFormData")
.param("id", "1")
.param("name", "John Doe"));
5. MockMvc 的局限性
尽管 MockMvc
使用便捷,但存在以下限制:
非真实网络调用
- 使用
TestDispatcherServlet
模拟请求处理 - ❌ 不会建立真实网络连接
- ❌ 无法测试完整网络栈
- 使用
功能支持不全
- ⚠️ 不支持 HTTP 重定向(如 Spring Boot 的
/error
接口) - ⚠️ 部分过滤器/拦截器可能失效
- ⚠️ 不支持 HTTP 重定向(如 Spring Boot 的
替代方案(真实 HTTP 测试):
@SpringBootTest(webEnvironment = DEFINED_PORT)
public class GreetControllerRealIntegrationTest {
@Before
public void setUp() {
RestAssured.port = DEFAULT_PORT;
}
@Test
public void givenGreetURI_whenSendingReq_thenVerifyResponse() {
given().get("/greet")
.then()
.statusCode(200);
}
}
✅ 优势:
- 真实 HTTP 请求/响应
- 测试覆盖更完整
6. 总结
本文通过实战演示了 Spring 集成测试的核心要点:
配置关键
@ExtendWith(SpringExtension.class)
启用 Spring@WebAppConfiguration
加载 Web 上下文MockMvc
封装测试环境
测试场景覆盖
- 视图名称验证
- JSON 响应体断言
- 路径变量/查询参数传递
- POST 请求处理
局限性认知
- 明确
MockMvc
的模拟边界 - 复杂场景考虑真实 HTTP 测试
- 明确
💡 完整代码示例见 GitHub 仓库