You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中findInContextUser方法的JUnit Mock测试报错排查

问题分析与解决方案

你的测试抛出401错误,核心原因有三个:

  1. SecurityContext未注入到SecurityContextHolder:你创建了SecurityContext mock,但没有把它设置到Spring Security的SecurityContextHolder中,导致findInContextUser方法获取到的Authentication为null。
  2. Authentication的mock逻辑错误:你直接链式mocksecurityContext.getAuthentication().getPrincipal(),这不符合Mockito的mock规则,应该先单独mockauthentication.getPrincipal()的返回值。
  3. 未mockfindUserByUsername方法: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();
    }
}

关键修正点说明

  1. 注入SecurityContext到Holder:
    通过SecurityContextHolder.setContext(securityContext)把mock的上下文设置到Spring Security的上下文持有者中,确保findInContextUser能获取到我们预设的认证信息。

  2. 正确mock认证对象:
    先单独mockauthentication.getPrincipal()返回UserDetails,再让SecurityContext返回这个Authentication,避免链式mock的错误写法。

  3. mock用户查询逻辑:
    getUserDto依赖findUserByUsername获取用户数据,测试中必须mock这个方法的返回值,否则会执行真实查询逻辑导致找不到用户。如果该方法是UserService自身的方法,要用doReturn来mock,避免触发真实方法。

  4. 上下文清理:
    在@AfterEach方法中清理SecurityContextHolder,保证每个测试用例的上下文隔离,不会互相干扰。

  5. 补充用户ID:
    原测试中未设置User的id,会导致预期和实际的UserDto id不匹配,断言失败,所以需要手动指定id值。

内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 00:30:53