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

测试OAuth2响应式WebClient遇serverWebExchange空值错误求助

解决Reactive WebClient OAuth2测试中serverWebExchange cannot be null错误

问题场景

作为Java响应式编程新手,在编写Reactive WebClient的测试时遇到问题:配置的DynamicsWebClientConfig在正常运行时能完成OAuth2认证并请求数据,但测试时抛出serverWebExchange cannot be null错误,报错位置为org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager.lambda$authorize$4。

配置类代码

@Configuration
public class DynamicsWebClientConfig {

    @Bean
    WebClient dynamicsClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
                new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth2Client.setDefaultClientRegistrationId("dynamics");
        return WebClient.builder()
                .filter(oauth2Client)
                .build();

    }
}

测试类代码

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(locations = "classpath:application-test.properties")
public class DynamicsClientTest {
    @Autowired
    private WebClient dynamicsClient;

    @Test
    public void testDynamicsClient() {
        String responseBody = dynamicsClient.get()
                .uri("https://api.businesscentral.dynamics.com/<some_ids_an_context_here>/customers")
                .retrieve()
                .bodyToMono(String.class)
                .block();

        assertNotNull(responseBody);
        assertEquals("expectedData", responseBody);
    }
}

问题原因

DefaultReactiveOAuth2AuthorizedClientManager是为Web场景设计的,依赖ServerWebExchange获取请求上下文,但在测试中直接调用WebClient时,没有触发完整的Web请求流程,也就没有可用的ServerWebExchange实例,导致报错。

解决方案

方案1:替换测试环境的AuthorizedClientManager实现

在测试环境中使用不需要ServerWebExchange的AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager,覆盖原有Bean:

  1. 创建测试专用配置类
@TestConfiguration
public class TestDynamicsWebClientConfig {

    @Bean
    ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
            ReactiveClientRegistrationRepository clientRegistrationRepository,
            ReactiveOAuth2AuthorizedClientService authorizedClientService) {
        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager manager =
                new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientService);
        return manager;
    }
}
  1. 在测试类中导入该配置
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(locations = "classpath:application-test.properties")
@Import(TestDynamicsWebClientConfig.class)
public class DynamicsClientTest {
    @Autowired
    private WebClient dynamicsClient;

    @Test
    public void testDynamicsClient() {
        String responseBody = dynamicsClient.get()
                .uri("https://api.businesscentral.dynamics.com/<some_ids_an_context_here>/customers")
                .retrieve()
                .bodyToMono(String.class)
                .block();

        assertNotNull(responseBody);
        assertEquals("expectedData", responseBody);
    }
}

方案2:模拟OAuth2认证流程(推荐用于单元测试)

如果不需要真实的OAuth2认证,直接模拟请求头中的Bearer Token,同时可以用WireMock模拟外部API的响应:

@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
public class DynamicsClientTest {

    @Autowired
    private WebClient.Builder webClientBuilder;

    private WebClient dynamicsClient;

    @BeforeEach
    void setUp() {
        // 模拟OAuth2访问令牌
        String mockToken = "mock-valid-token";
        // 构建带有模拟认证头的WebClient
        dynamicsClient = webClientBuilder
                .filter(request -> request.exchange(
                        ClientRequest.from(request.request())
                                .header("Authorization", "Bearer " + mockToken)
                                .build()
                ))
                .build();
    }

    @Test
    public void testDynamicsClient() {
        // 建议用WireMock模拟目标API的返回,避免真实调用
        String responseBody = dynamicsClient.get()
                .uri("https://api.businesscentral.dynamics.com/<some_ids_an_context_here>/customers")
                .retrieve()
                .bodyToMono(String.class)
                .block();

        assertNotNull(responseBody);
        assertEquals("expectedData", responseBody);
    }
}

方案3:结合@WebFluxTest加载配置类

如果想用@WebFluxTest,手动导入配置类并替换AuthorizedClientManager:

@WebFluxTest
@TestPropertySource(locations = "classpath:application-test.properties")
@Import({DynamicsWebClientConfig.class, TestDynamicsWebClientConfig.class})
public class DynamicsClientTest {
    @Autowired
    private WebClient dynamicsClient;

    @Test
    public void testDynamicsClient() {
        String responseBody = dynamicsClient.get()
                .uri("https://api.businesscentral.dynamics.com/<some_ids_an_context_here>/customers")
                .retrieve()
                .bodyToMono(String.class)
                .block();

        assertNotNull(responseBody);
        assertEquals("expectedData", responseBody);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 04:57:25