Controller 集成测试指南
本文档展示如何使用 MockMvc 测试 Controller 层,基于项目实际的 AuthControllerTest 实现。
📁 测试文件位置
blog-modules/blog-module-system/blog-system-service/src/test/java/
└── com/blog/system/controller/
├── BaseControllerTest.java # 测试基类(共享配置)
├── AuthControllerTest.java # 认证接口测试
├── UserControllerTest.java # 用户接口测试
└── RoleControllerTest.java # 角色接口测试
blog-application/src/test/java/
└── com/blog/
├── security/SecurityIntegrationTest.java
├── file/FileStorageIntegrationTest.java
└── redis/RedisUtilsTest.java
🏗️ 测试基类设计
所有 Controller 测试继承自 BaseControllerTest:
@SpringBootTest(classes = TestBlogSystemApplication.class)
@AutoConfigureMockMvc
public abstract class BaseControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper;
@MockitoBean
protected IUserService userService;
@MockitoBean
protected IRoleService roleService;
}
测试专用 Application
@SpringBootApplication(
scanBasePackages = "com.blog.system.controller",
exclude = { DataSourceAutoConfiguration.class }
)
@Import(TestSecurityConfig.class)
@EnableMethodSecurity
public class TestBlogSystemApplication {
public static void main(String[] args) {
SpringApplication.run(TestBlogSystemApplication.class, args);
}
}
测试安全配置
@TestConfiguration
@EnableWebSecurity
public class TestSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/auth/**").permitAll()
.anyRequest().authenticated());
return http.build();
}
}
📝 AuthController 测试示例
class AuthControllerTest extends BaseControllerTest {
@Test
void should_register_success() throws Exception {
// Given
RegisterDTO registerDTO = new RegisterDTO();
registerDTO.setUsername("testuser");
registerDTO.setPassword("Password123!");
registerDTO.setEmail("test@example.com");
registerDTO.setNickname("testnick");
UserVO userVO = new UserVO();
userVO.setId(1L);
userVO.setUsername("testuser");
userVO.setEmail("test@example.com");
when(userService.register(any(RegisterDTO.class))).thenReturn(userVO);
// When & Then
mockMvc.perform(post("/api/v1/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(registerDTO)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(0))
.andExpect(jsonPath("$.data.username").value("testuser"));
}
@Test
void should_register_fail_when_invalid_dto() throws Exception {
// Given - 缺少必填字段
RegisterDTO registerDTO = new RegisterDTO();
// When & Then - 期望 400 Bad Request
mockMvc.perform(post("/api/v1/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(registerDTO)))
.andExpect(status().isBadRequest());
}
@Test
void should_login_success() throws Exception {
// Given
LoginDTO loginDTO = new LoginDTO();
loginDTO.setUsername("testuser");
loginDTO.setPassword("password123");
LoginVO loginVO = new LoginVO();
loginVO.setToken("access_token");
when(userService.login(any(LoginDTO.class))).thenReturn(loginVO);
// When & Then
mockMvc.perform(post("/api/v1/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(loginDTO)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(0))
.andExpect(jsonPath("$.data.token").value("access_token"));
}
@Test
void should_logout_success() throws Exception {
mockMvc.perform(post("/api/v1/auth/logout"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(0));
}
}
⚠️ 常见问题
1. 403 Forbidden 错误
原因: TestSecurityConfig 中的路径匹配规则与实际 API 路径不一致。
// ❌ 错误 - 路径不匹配
.requestMatchers("/auth/**").permitAll()
// ✅ 正确 - 包含完整 API 前缀
.requestMatchers("/api/v1/auth/**").permitAll()
2. @MockBean vs @MockitoBean
Spring Boot 3.4+ 推荐使用 @MockitoBean:
// ⚠️ Deprecated (Spring Boot 3.4+)
@MockBean
private IUserService userService;
// ✅ 推荐 (Spring Boot 3.5+)
@MockitoBean
private IUserService userService;
3. 响应格式验证
项目使用统一响应格式 Result<T>:
// 验证成功响应
.andExpect(jsonPath("$.code").value(0))
.andExpect(jsonPath("$.data.username").value("testuser"))
// 验证错误响应
.andExpect(jsonPath("$.code").value(400))
.andExpect(jsonPath("$.message").exists())
🚀 运行测试
# 运行 Controller 测试
mvn test -Dtest=AuthControllerTest
# 运行所有 Controller 测试
mvn test -Dtest=*ControllerTest
📊 测试覆盖
| 接口 | 测试场景 | 状态 |
|---|---|---|
POST /api/v1/auth/register | 成功 / 参数无效 | ✅ |
POST /api/v1/auth/login | 成功 | ✅ |
POST /api/v1/auth/logout | 成功 | ✅ |
GET /api/v1/users | 需认证 | ⏳ |
PUT /api/v1/users/{id} | 需认证 | ⏳ |