为什么要组合Mockito ?
答:隔离 Controller 层,保证单元测试纯粹性
具体来说:
- Controller 层通常依赖 Service、Repository 等组件,而这些组件可能涉及数据库、外部 API 或复杂逻辑,如果我们单纯使用JUnit ,我们实际上进行的就变成了集成测试。
- Mockito 允许我们模拟(Mock)这些依赖,避免真实调用数据库或外部服务,使测试更快速、稳定。
如何实现?
导入依赖
以maven为例,导入的依赖为:
<!--Spring Boot 默认使用 JUnit 作为主要的测试框架,并集成了 Mockito 作为 Mock 测试工具。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--用于测试运行器(如 IDE 或 Maven Surefire 插件)加载和执行 JUnit 测试-->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
新建测试类
被测类:
@RestController
@RequestMapping("/api")
public class MyController {
private final MyService myService;
@Autowired
public MyController(MyService myService) {
this.myService = myService;
}
@GetMapping("/data/{id}")
public Result<String> getSomething(@PathVariable int id) {
try {
String data = myService.getDataById(id);
if (data == null) {
return Result.fail("Data not found");
}
return Result.ok(data);
} catch (Exception e) {
return Result.fail("Server error");
}
}
}
测试类:
class MyControllerTest {
@Test
void testGetSomething() {
}
}
类注解
//启动完整的 Spring Boot 容器
@SpringBootTest
// 自动配置 MockMvc,允许在不真正启动 Web 服务器的情况下,模拟 HTTP 请求
@AutoConfigureMockMvc
// 扩展 JUnit 5,使其支持 Spring Test 框架。
@ExtendWith(SpringExtension.class)
class MyControllerTest {
// 自动注入 MockMvc
@Autowired
private MockMvc mockMvc;
// Mock Service 层,避免真实调用数据库
@MockBean
private MyService myService;
@Test
void testGetSomething() throws Exception {
}
}
方法逻辑
1. 定义替换逻辑
@SpringBootTest
@AutoConfigureMockMvc
@ExtendWith(SpringExtension.class)
class MyControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private MyService myService; // Mock Service,避免真实调用
@Test
void testGetSomething_Success() throws Exception {
// 解析见下文
when(myService.getDataById(anyInt())).thenReturn(Result.success("test001"));
}
}
when(myService.getDataById(anyInt())).thenReturn("Data for ID 1");
when(myService.getDataById(...))
用于指定 mock 行为,表示当 myService.getDataById(id) 被调用时,应该返回特定值。anyInt()
是Mockito 的参数匹配器,表示无论传入的 id 是什么整数值,都返回Result.success("test001")
。thenReturn(Result.success("test001"))
指定返回的模拟数据。
2. 定义模拟操作
@SpringBootTest
@AutoConfigureMockMvc
@ExtendWith(SpringExtension.class)
class MyControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private MyService myService; // Mock Service,避免真实调用
@Test
void testGetSomething_Success() throws Exception {
when(myService.getDataById(anyInt())).thenReturn(Result.success("test001"));
//模拟
mockMvc.perform(get("/api/data/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data").isNotEmpty());
}
}
mockMvc.perform(get("/api/data/1")) .andExpect(status().isOk()) .andExpect(jsonPath("$.data").isNotEmpty())
mockMvc.perform(...)
执行一个模拟的 HTTP 请求,这里是 GET请求,目标 URL 为/api/data/1
。- 这个请求会调用
MyController的getSomething(int id)
方法,模拟客户端访问/api/data/1
andExpect(status().isOk())
:断言(Assertion)HTTP 响应状态码为 200 OKandExpect(jsonPath("$.data").isNotEmpty())
:假设Result结构是通用的code、msg和data,此处简单的测试一下有返回结果。
.andExpect(...)
方法简述
status().isOk()
- 验证 HTTP 状态码content().string("text")
- 验证响应体(字符串)content().json("{...}")
- 验证 JSON 结构header().string("Content-Type", "application/json")
- 验证 HTTP 头jsonPath("$.id").value(1)
- 验证 JSON 字段cookie().exists("SESSIONID")
- 验证 Cookieview().name("home")
- 验证返回的视图名称(Spring MVC)
3. 确保执行
@SpringBootTest
@AutoConfigureMockMvc
@ExtendWith(SpringExtension.class)
class MyControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private MyService myService; // Mock Service,避免真实调用
@Test
void testGetSomething_Success() throws Exception {
when(myService.getDataById(anyInt())).thenReturn(Result.success("test001"));
mockMvc.perform(get("/api/data/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data").isNotEmpty());
//确保执行过
verfy(myService).getDataById(anyInt());
}
}
verfy(myService).getDataById(anyInt());
- 检查
myService.getDataById(int)
方法是否被调用过。 anyInt()
代表不关心具体传入的整数值,只要getDataById()
被调用了就算测试通过。
拓展:
verify(myService, never()).getDataById(1); // 确保从未调用
verify(myService, times(2)).getDataById(1); // 必须调用 2 次
verify(myService, atLeast(1)).getDataById(1); // 至少 1 次
verify(myService, atMost(3)).getDataById(1); // 最多 3 次
完整示例
@SpringBootTest
@AutoConfigureMockMvc
@ExtendWith(SpringExtension.class)
class MyControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private MyService myService; // Mock Service,避免真实数据库调用
@Test
void testGetSomething_Success() throws Exception {
when(myService.getDataById(1)).thenReturn(Result.success("test001"));
mockMvc.perform(get("/api/data/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(Result.success().getCode()))
.andExpect(jsonPath("$.data").isNotEmpty())
.andExpect(jsonPath("$.data").value("test001"));
//确保执行过
verfy(myService).getDataById(1);
}
@Test
void testGetSomething_NotFound() throws Exception {
when(myService.getDataById(2)).thenReturn(Result.fail("error"));
mockMvc.perform(get("/api/data/2"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(Result.fail("").getCode()))
.andExpect(jsonPath("$.msg").isNotEmpty());
//确保执行过
verfy(myService).getDataById(2);
}
}