测试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:
- 创建测试专用配置类
@TestConfiguration public class TestDynamicsWebClientConfig { @Bean ReactiveOAuth2AuthorizedClientManager authorizedClientManager( ReactiveClientRegistrationRepository clientRegistrationRepository, ReactiveOAuth2AuthorizedClientService authorizedClientService) { AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager manager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientService); return manager; } }
- 在测试类中导入该配置
@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
相关产品推荐
相关产品推荐

