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所有依赖:必须mock
postService、friendService等Controller用到的所有服务,否则会因为未初始化的Bean抛出异常。 - 修正状态码期望:登录后请求受保护端点应返回200 OK,而非401未授权。
内容的提问来源于stack exchange,提问作者cumbainer
相关产品推荐
相关产品推荐

