跳到主要内容

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;

详见 MockBean 迁移指南

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}需认证