HTTP 测试
Laravel 用
$this->get('/posts')+assertStatus;Spring Boot 用 MockMvc 模拟 HTTP 请求、断言状态码和响应体。MockMvc 不打真实端口,直接在内存里跑完整 MVC 链路 —— 又全又省事。
依赖
spring-boot-starter-test 已包含 MockMvc 和 JUnit 5,只需引入:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>第一个 HTTP 测试
java
package com.example.blog.post;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
class PostControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void 获取帖子列表返回200() throws Exception {
mockMvc.perform(get("/api/posts"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(2)); // JSON 数组长度
}
@Test
void 查询不存在的帖子返回404() throws Exception {
mockMvc.perform(get("/api/posts/99999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.code").value(404));
}
}对应 Laravel
| Laravel | MockMvc |
|---|---|
$this->get('/posts') | mockMvc.perform(get("/api/posts")) |
->assertStatus(200) | .andExpect(status().isOk()) |
->assertJson(['title' => ...]) | .andExpect(jsonPath("$.title").value(...)) |
->assertJsonCount(2) | .andExpect(jsonPath("$.length()").value(2)) |
常用请求构造
java
// GET + 查询参数
mockMvc.perform(get("/api/posts")
.param("page", "1")
.param("size", "10"));
// POST + JSON body
mockMvc.perform(post("/api/posts")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\":\"测试\",\"content\":\"内容\"}"));
// 带请求头(如 token)
mockMvc.perform(get("/api/me")
.header("Authorization", "Bearer test-token"));
// 路径参数
mockMvc.perform(get("/api/posts/{id}", 1));常用断言
java
// 状态码
.andExpect(status().isOk()) // 200
.andExpect(status().isCreated()) // 201
.andExpect(status().isNoContent()) // 204
.andExpect(status().isUnauthorized()) // 401
.andExpect(status().isForbidden()) // 403
.andExpect(status().isNotFound()) // 404
.andExpect(status().isBadRequest()) // 400
// JSON 结构
.andExpect(jsonPath("$.data.title").value("测试"))
.andExpect(jsonPath("$.data.status").isNumber())
.andExpect(jsonPath("$.data.list[0].id").value(1))
.andExpect(jsonPath("$.data").isEmpty())
// 响应头
.andExpect(header().string("Content-Type", containsString("application/json")))TIP
jsonPath 是 MockMvc 最强大的断言工具,用 JSONPath 语法查 JSON 任意位置($.data[0].title 等)。配合 @RestControllerAdvice 的统一响应结构,断言 $.code / $.message 非常顺。
集成测试要启动 Spring 吗
@SpringBootTest 会启动完整应用上下文。几个常用注解:
| 注解 | 行为 |
|---|---|
@SpringBootTest | 启动完整上下文(真实注入所有 Bean) |
@AutoConfigureMockMvc | 自动配置 MockMvc |
@WebMvcTest(PostController.class) | 只加载 Web 层(快,Controller 单测用) |
@DataJpaTest | 只测 JPA 层(见 数据库测试) |
java
// 轻量级:只测 Controller,Service 用 Mock 替身
@WebMvcTest(PostController.class)
class PostControllerWebTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private PostService postService;
@Test
void 列表接口() throws Exception {
when(postService.list()).thenReturn(List.of(...));
mockMvc.perform(get("/api/posts")).andExpect(status().isOk());
}
}@WebMvcTest vs @SpringBootTest
@WebMvcTest:只装配 Web 层,快,适合 Controller 单测(依赖 Mock)@SpringBootTest:全量装配,慢,适合链路验证(真实数据库) Laravel 的Feature测试是全栈的,最接近@SpringBootTest。
完整请求-响应断言示例
java
@Test
void 创建帖子成功() throws Exception {
String body = """
{"title":"Spring 测试","content":"正文内容"}
""";
mockMvc.perform(post("/api/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.code").value(0))
.andExpect(jsonPath("$.data.title").value("Spring 测试"));
}
@Test
void 标题为空校验失败() throws Exception {
String body = "{\"title\":\"\",\"content\":\"x\"}";
mockMvc.perform(post("/api/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.code").value(400));
}响应 JSON 复杂断言
java
// 数组长度
jsonPath("$.data.items.length()").value(3)
// 是否存在某个字段
jsonPath("$.data.token").exists()
// 是否匹配正则
jsonPath("$.data.phone").value(matchesPattern("^1\\d{10}$"))实践建议
- Controller 测「请求→响应」契约:状态码、JSON 结构、校验错误 —— 别测业务逻辑
- 业务逻辑留给 Service 测试(模拟对象 Mockito)
- 用中文方法名描述行为
- 与数据库打交道的测试见 数据库测试
下一节:数据库测试。