Kotlin编写Spring Controller集成测试失败,依赖注入与鉴权参数传递异常
核心问题修复
1. Controller依赖为空的问题
你在@BeforeEach中手动重写已被@Autowired注入的usersService、userController等变量的行为完全错误:你修改的只是测试类中的变量值,Spring容器中管理的Controller、Service实例不会同步更新,自然运行时Controller拿到的是未初始化的旧实例,出现null值。
直接删除@BeforeEach中所有手动给@Autowired变量赋值的代码,所有依赖由Spring容器全权管理,仅保留测试数据的插入逻辑即可。
2. @AuthenticationPrincipal不生效的问题
首先确保测试类开启了权限注解支持:Spring Security 6+添加@EnableMethodSecurity(jsr250Enabled = true),低版本添加@EnableGlobalMethodSecurity(jsr250Enabled = true),保证@RolesAllowed注解可以正常触发。
针对自定义的UserPrincipal类型,可选两种方案注入认证信息:
- 方案1:请求时直接传入认证信息
调用mockMvc.perform时添加with(SecurityMockMvcRequestPostProcessors.user(userPrincipal))即可直接注入自定义身份信息,不需要额外注解:mockMvc.perform( MockMvcRequestBuilders.get("/users/") .accept(MediaType.APPLICATION_JSON) .with(SecurityMockMvcRequestPostProcessors.user(userPrincipal)) ) - 方案2:自定义测试注解简化复用
定义绑定自定义UserPrincipal的测试注解,后续测试直接加注解即可:@Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @WithSecurityContext(factory = WithMockUserPrincipalSecurityContextFactory::class) annotation class WithMockUserPrincipal( val id: String = "1", val username: String = "admin", val role: String = ROLE_ADMIN ) class WithMockUserPrincipalSecurityContextFactory : WithSecurityContextFactory<WithMockUserPrincipal> { override fun createSecurityContext(annotation: WithMockUserPrincipal): SecurityContext { val user = User(id = UserId(annotation.id), username = annotation.username, role = annotation.role) val principal = UserPrincipal(user, null) val authentication = UsernamePasswordAuthenticationToken(principal, null, listOf(SimpleGrantedAuthority(annotation.role))) return SecurityContextHolder.createEmptyContext().apply { this.authentication = authentication } } }
3. 测试随机报错(上下文残留)的问题
你已添加的@DirtiesContext注解本身可以实现上下文清理,问题是手动修改Spring容器管理的bean会导致清理逻辑失效,修复第一条的手动赋值问题后即可恢复正常。另外测试类中@MockBean private lateinit var userPrincipal: UserPrincipal属于多余配置,直接删除避免干扰Security上下文注入。
完整测试代码示例
@SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("test") @EnableMethodSecurity(jsr250Enabled = true) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) open class UsersControllerTest { @Autowired lateinit var mockMvc: MockMvc @Autowired lateinit var mapper: ObjectMapper @Autowired lateinit var userRepository: UserRepository private val validUser = User(UserId("1"), "admin", ROLE_ADMIN) @BeforeEach fun init(){ userRepository.insert(validUser) } @AfterEach fun cleanup() { userRepository.deleteAll() SecurityContextHolder.clearContext() } @Test @WithMockUserPrincipal fun `校验管理员查询用户列表逻辑正常`() { mockMvc.perform( MockMvcRequestBuilders.get("/users/") .param("admin", "true") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) ).andExpect(MockMvcResultMatchers.status().isOk) .andExpect(MockMvcResultMatchers.jsonPath("$").isArray) } @Test @WithMockUserPrincipal fun `校验参数非法时返回400错误`() { mockMvc.perform( MockMvcRequestBuilders.get("/users/") .param("admin", "非法字符串") .accept(MediaType.APPLICATION_JSON) ).andExpect(MockMvcResultMatchers.status().isBadRequest) } }
内容的提问来源于stack exchange,提问作者Jasper-ui
相关产品推荐
相关产品推荐

