Spring Boot中findInContextUser方法的JUnit Mock测试报错排查
问题分析与解决方案
你的测试抛出401错误,核心原因有三个:
- SecurityContext未注入到SecurityContextHolder:你创建了SecurityContext mock,但没有把它设置到Spring Security的
SecurityContextHolder中,导致findInContextUser方法获取到的Authentication为null。 - Authentication的mock逻辑错误:你直接链式mock
securityContext.getAuthentication().getPrincipal(),这不符合Mockito的mock规则,应该先单独mockauthentication.getPrincipal()的返回值。 - 未mock
findUserByUsername方法:getUserDto依赖findUserByUsername查询用户,测试中如果不mock这个方法,会执行真实逻辑导致找不到用户(或抛出其他异常)。
另外,@WithMockUser注解仅在Spring MVC测试(如@WebMvcTest)或集成测试中生效,Service层测试需要手动操作SecurityContextHolder。
修正后的测试代码
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.authority.SimpleGrantedAuthority; import java.util.stream.Collectors; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class UserServiceTest { @Mock // 替换为你实际的用户查询Repository/DAO类 private UserRepository userRepository; @InjectMocks private UserService userService; @AfterEach void tearDown() { // 测试结束后清理上下文,避免影响其他测试 SecurityContextHolder.clearContext(); } @Test void itShouldFindInContextUser() { // given - 前置条件准备 User user = User.builder() .id(1L) // 必须设置id,否则断言会因id不匹配失败 .username("username") .password("password") .role(Role.USER) .build(); UserDto expected = UserDto.builder() .id(user.getId()) .username(user.getUsername()) .role(user.getRole()) .build(); // 1. Mock用户查询方法,返回预设的user对象 when(userRepository.findUserByUsername(user.getUsername())).thenReturn(user); // 如果findUserByUsername是UserService自身的方法,改用下面的写法: // doReturn(user).when(userService).findUserByUsername(user.getUsername()); // 2. 构造认证相关对象 var roles = Stream.of(user.getRole()) .map(role -> new SimpleGrantedAuthority(role.name())) .collect(Collectors.toList()); UserDetails userDetails = new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), roles ); Authentication auth = mock(Authentication.class); when(auth.getPrincipal()).thenReturn(userDetails); // 3. 将mock的SecurityContext注入到SecurityContextHolder SecurityContext securityContext = mock(SecurityContext.class); when(securityContext.getAuthentication()).thenReturn(auth); SecurityContextHolder.setContext(securityContext); // when - 执行待测试方法 UserDto actual = userService.findInContextUser(); // then - 验证结果 assertEquals(expected, actual); assertEquals(expected.getUsername(), actual.getUsername()); // 验证方法调用次数 verify(userRepository, times(1)).findUserByUsername(user.getUsername()); verify(securityContext, times(1)).getAuthentication(); verify(auth, times(1)).getPrincipal(); } }
关键修正点说明
注入SecurityContext到Holder:
通过SecurityContextHolder.setContext(securityContext)把mock的上下文设置到Spring Security的上下文持有者中,确保findInContextUser能获取到我们预设的认证信息。正确mock认证对象:
先单独mockauthentication.getPrincipal()返回UserDetails,再让SecurityContext返回这个Authentication,避免链式mock的错误写法。mock用户查询逻辑:
getUserDto依赖findUserByUsername获取用户数据,测试中必须mock这个方法的返回值,否则会执行真实查询逻辑导致找不到用户。如果该方法是UserService自身的方法,要用doReturn来mock,避免触发真实方法。上下文清理:
在@AfterEach方法中清理SecurityContextHolder,保证每个测试用例的上下文隔离,不会互相干扰。补充用户ID:
原测试中未设置User的id,会导致预期和实际的UserDto id不匹配,断言失败,所以需要手动指定id值。
内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu
相关产品推荐
相关产品推荐

