Spring Boot单元测试中如何正确Mock SecurityContextHolder
问题原因
原测试代码运行失败有三个直接原因:
- 变量名拼写错误:
setUp方法中声明的Mock认证对象变量名为auth,但给SecurityContext打桩时传入的是未定义的authentication,会直接触发运行时错误。 - 核心方法未打桩:Mock生成的
Authentication实例没有配置getAuthorities()方法的返回值,Mockito默认对集合返回值返回空列表,业务代码中判断角色是否存在永远得到false,无法进入预期逻辑分支。 - 缺少测试后清理逻辑:
SecurityContextHolder基于ThreadLocal存储上下文,测试执行完成后如果不主动清空,上下文会残留到同线程执行的其他测试用例中,污染测试结果。
正确实现方案
方案1:纯Mockito手动Mock实现
适合不加载Spring上下文的纯单元测试场景,代码如下:
import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import java.util.List; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class UserApiTest { @Before public void setUp() { // 构造当前测试场景需要的角色权限 List<GrantedAuthority> testAuthorities = List.of( new SimpleGrantedAuthority("ROLE_roleName1") ); // Mock认证对象,配置权限返回值 Authentication auth = mock(Authentication.class); when(auth.getAuthorities()).thenReturn(testAuthorities); // Mock安全上下文,绑定认证对象 SecurityContext securityContext = mock(SecurityContext.class); when(securityContext.getAuthentication()).thenReturn(auth); // 将上下文设置到Holder中 SecurityContextHolder.setContext(securityContext); } @After public void tearDown() { // 测试完成后清空上下文,避免跨用例污染 SecurityContextHolder.clearContext(); } @Test public void testRole1BusinessLogic() { // 编写对应角色的接口测试逻辑即可 } }
如果要测试ROLE_roleName2的分支,只需要把setUp中构造的权限替换为对应角色即可,也可以根据不同测试用例单独构造上下文。
方案2:使用Spring Security Test注解实现(推荐)
如果是集成测试场景,推荐直接引入spring-security-test测试依赖,通过注解直接模拟登录用户,不需要手动编写Mock代码,出错概率更低:
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserApiIntegrationTest { @Test @WithMockUser(roles = "roleName1") // 注意roles参数不需要手动加ROLE_前缀,框架会自动拼接 public void testRole1Access() { // 直接发起接口请求测试逻辑,框架会自动填充SecurityContext } @Test @WithMockUser(roles = "roleName2") public void testRole2Access() { // 测试第二个角色的逻辑分支 } }
注意:如果需要配置非角色类的自定义权限,可以使用@WithMockUser(authorities = "CUSTOM_PERMISSION")直接传入完整权限字符串,框架不会自动拼接前缀。
内容的提问来源于stack exchange,提问作者Vinutha
相关产品推荐
相关产品推荐

