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

在@SpringBootTest中使用WebTestClient模拟JWT Token进行接口测试的问题

你的问题解答

1. WebTestClient vs WebClient:选哪个?

绝对应该用WebTestClient,它是Spring官方专为测试Web应用(不管是Spring MVC还是WebFlux)设计的客户端,支持真实HTTP调用(当你用WebEnvironment.RANDOM_PORT时),还集成了Spring Security的测试支持。而WebClient是生产环境中用来发起HTTP请求的工具,不是为测试场景设计的,完全没必要改用它。

2. 解决NoSuchBeanDefinitionException: No bean named 'webHandler' available异常

这个错误的核心原因是:你用了bindToApplicationContext(context)构建WebTestClient——这个方法是为**WebFlux(反应式Web框架)**设计的,但你的项目是基于Spring MVC(Servlet栈)的,所以找不到对应的webHandler bean。

修正方案非常简单:

  • 最简单的方式是直接**@Autowired WebTestClient**,因为你已经添加了@AutoConfigureWebTestClient注解,Spring会自动为你配置好连接到随机端口的WebTestClient。
  • 如果你坚持手动构建,应该用bindToServer()指定服务器端口,而非绑定到ApplicationContext。

修改后的测试类示例:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
public class UserControllerIT {
    @Autowired
    private WebTestClient webTestClient; // 直接注入,无需手动构建

    @MockBean
    private UserRepo userRepo;

    @Test
    public void getUser_WithValidJwt_ShouldReturn200() {
        // 1. 准备测试数据
        final User user = createUser("foo@bar.com", "my-password");
        when(userRepo.findById(anyLong())).thenReturn(Optional.of(user));

        // 2. 模拟JWT请求:两种方式任选其一
        // 方式一:直接添加Authorization Header(更贴近真实请求场景)
        String validToken = generateValidJwtToken(user.getId()); // 用你的JWT工具类生成有效Token
        webTestClient
                .get()
                .uri("/user")
                .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)
                .exchange()
                .expectStatus().isOk();

        // 方式二:用Spring Security测试支持模拟JWT认证(适合无需真实Token解析的场景)
        webTestClient
                .mutateWith(mockJwt().jwt(jwt -> jwt.subject(user.getId().toString())))
                .get()
                .uri("/user")
                .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
                .exchange()
                .expectStatus().isOk();
    }

    // 辅助方法:生成符合要求的JWT Token(根据你的项目JWT配置实现)
    private String generateValidJwtToken(Long userId) {
        return Jwts.builder()
                .setSubject(userId.toString())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 3600000))
                .signWith(SignatureAlgorithm.HS512, "your-jwt-secret-key") // 替换为你项目的JWT密钥
                .compact();
    }
}

3. JWT模拟的补充说明

  • 如果你的JwtRequestFilter会严格验证Token的签名和有效期,那必须生成符合要求的真实Token,否则过滤器会直接拒绝请求。
  • 如果你不想依赖真实的Token生成逻辑,可以直接mockJwtRequestFilter的行为,让它跳过Token解析,直接设置你需要的认证信息:
@MockBean
private JwtRequestFilter jwtRequestFilter;

@BeforeEach
public void setup() {
    User testUser = createUser("foo@bar.com", "my-password");
    CustomUserDetails userDetails = new CustomUserDetails(testUser);
    Authentication auth = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    
    // 让过滤器直接设置认证上下文
    doAnswer(invocation -> {
        SecurityContextHolder.getContext().setAuthentication(auth);
        return null;
    }).when(jwtRequestFilter).doFilterInternal(any(HttpServletRequest.class), any(HttpServletResponse.class), any(FilterChain.class));
}

这种方式适合快速测试控制器的业务逻辑,无需关心Token的生成和解析细节。

4. 移除不必要的注解

你的测试方法上的@WithMockUser可以删掉,因为这个注解是模拟普通的用户名密码认证,和JWT认证场景不匹配,反而可能干扰测试逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 08:32:57