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

MockMVC单元测试授权:解决@AuthenticationPrincipal空指针问题

解决@AuthenticationPrincipal测试时的空指针问题

你的问题核心是:@WithMockUser默认生成的认证主体是字符串类型,而你的Controller期望注入自定义的SecurityUser对象,导致u为null抛出空指针异常。以下是具体解决方案:

方案一:手动构建认证对象注入

在测试方法中直接创建SecurityUser和对应的Authentication对象,通过MockMvc的principal()方法传入请求:

@Test
public void getUserByIdTest() throws Exception {
    // 构建测试用User实体
    User user = new User();
    user.setFirstName("FirstTest");
    user.setLastName("LastTest");
    user.setPassword("q");
    user.setEmail("email@ua.com");
    user.setUsername("q");
    user.setId(444);
    // 初始化关联字段为空列表,避免后续调用size()/contains()时NPE
    user.setImages(new ArrayList<>());
    user.setReceivedRequests(new ArrayList<>());

    // 构建自定义SecurityUser
    SecurityUser securityUser = new SecurityUser(user);
    // 创建包含SecurityUser的认证对象
    Authentication auth = new UsernamePasswordAuthenticationToken(
            securityUser,
            null,
            Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
    );

    // Mock所有依赖的Service
    given(userService.readByUsername(user.getUsername())).willReturn(user);
    given(postService.getPostsByUser_Username(user.getUsername())).willReturn(new ArrayList<>());
    given(friendService.getFriendByReceiverUsername(user.getUsername(), securityUser.getUsername())).willReturn(null);

    mockMvc.perform(MockMvcRequestBuilders.get("/users/q")
                    .principal(auth)) // 注入自定义认证对象
            .andExpect(view().name("profile-page"))
            .andExpect(status().isOk()) // 登录后应为200,而非401
            .andExpect(model().attributeExists("user"))
            .andExpect(model().attribute("user", user));
}

方案二:自定义Mock用户注解(复用性更高)

如果多个测试都需要用到SecurityUser,可以自定义注解简化流程:

1. 创建自定义注解

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockSecurityUserFactory.class)
public @interface WithMockSecurityUser {
    String username() default "q";
    String[] roles() default {"USER"};
}

2. 实现SecurityContext工厂类

public class WithMockSecurityUserFactory implements WithSecurityContextFactory<WithMockSecurityUser> {
    @Override
    public SecurityContext createSecurityContext(WithMockSecurityUser annotation) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();

        // 构建测试用User并初始化关联字段
        User user = new User();
        user.setUsername(annotation.username());
        user.setImages(new ArrayList<>());
        user.setReceivedRequests(new ArrayList<>());

        SecurityUser securityUser = new SecurityUser(user);
        // 生成认证对象
        Authentication auth = new UsernamePasswordAuthenticationToken(
                securityUser,
                null,
                Arrays.stream(annotation.roles())
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList())
        );

        context.setAuthentication(auth);
        return context;
    }
}

3. 在测试中使用自定义注解

@Test
@WithMockSecurityUser(username = "q")
public void getUserByIdTest() throws Exception {
    User user = new User();
    user.setFirstName("FirstTest");
    user.setLastName("LastTest");
    user.setPassword("q");
    user.setEmail("email@ua.com");
    user.setUsername("q");
    user.setId(444);
    user.setImages(new ArrayList<>());
    user.setReceivedRequests(new ArrayList<>());

    given(userService.readByUsername(user.getUsername())).willReturn(user);
    given(postService.getPostsByUser_Username(user.getUsername())).willReturn(new ArrayList<>());
    given(friendService.getFriendByReceiverUsername(user.getUsername(), "q")).willReturn(null);

    mockMvc.perform(MockMvcRequestBuilders.get("/users/q"))
            .andExpect(view().name("profile-page"))
            .andExpect(status().isOk())
            .andExpect(model().attributeExists("user"))
            .andExpect(model().attribute("user", user));
}

关键注意事项

  • 初始化关联字段:确保User的images、receivedRequests等集合字段被初始化为空列表,避免Controller中调用size()、contains()时抛出空指针。
  • Mock所有依赖:必须mockpostService、friendService等Controller用到的所有服务,否则会因为未初始化的Bean抛出异常。
  • 修正状态码期望:登录后请求受保护端点应返回200 OK,而非401未授权。

内容的提问来源于stack exchange,提问作者cumbainer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 18:36:26