如何测试WebClient的ConnectTimeoutException与WriteTimeoutException?
没问题!我来帮你搞定ConnectTimeout和WriteTimeout的测试方法,结合你已经配置的Reactor WebClient,下面是具体的实现思路和代码示例:
测试ConnectTimeout
ConnectTimeout是TCP连接建立阶段的超时,我们只需要模拟一个无法建立连接的目标地址(比如不存在的IP/未监听的端口),同时设置极短的超时时间,就能触发这个异常。
这里用JUnit 5 + Reactor的StepVerifier来验证异常:
import io.netty.channel.ChannelOption; import io.netty.handler.timeout.ConnectTimeoutException; import org.junit.jupiter.api.Test; import reactor.netty.http.client.HttpClient; import reactor.test.StepVerifier; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.http.client.reactive.ReactorClientHttpConnector; @Test void testConnectTimeout() { // 配置一个极短的连接超时(100ms),确保快速触发异常 HttpClient httpClient = HttpClient.create() .tcpConfiguration(client -> client .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 100)); WebClient testWebClient = WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .baseUrl("http://192.168.99.99:9999") // 不存在的地址,无法建立TCP连接 .build(); // 发起请求并验证异常 Mono<String> request = testWebClient.get() .retrieve() .bodyToMono(String.class); StepVerifier.create(request) .expectError(ConnectTimeoutException.class) .verify(); }
原理:这个目标地址没有服务在监听,TCP握手无法完成,超过CONNECT_TIMEOUT_MILLIS设置的时间后,Netty会抛出ConnectTimeoutException,我们通过StepVerifier确认这个异常被正确抛出。
测试WriteTimeout
WriteTimeout是客户端向服务器发送数据时的超时,需要模拟一个接受连接但不读取任何数据的服务器,这样客户端的发送缓冲区会被填满,写操作会被阻塞直到超时。
我们可以用Netty快速搭建一个极简的测试服务器,配合StepVerifier验证异常:
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.timeout.WriteTimeoutException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.netty.http.client.HttpClient; import reactor.test.StepVerifier; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import java.net.InetSocketAddress; public class WebClientTimeoutTests { private NioEventLoopGroup serverGroup; private Channel serverChannel; @BeforeEach void setupTestServer() throws InterruptedException { // 启动一个Netty服务器,只接受连接但不处理数据 serverGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(serverGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { // 空处理器,不读取任何请求数据 ch.pipeline().addLast(new ChannelInboundHandlerAdapter()); } }); // 绑定随机可用端口 ChannelFuture future = bootstrap.bind(0).sync(); serverChannel = future.channel(); } @AfterEach void teardownTestServer() { // 关闭服务器资源 serverChannel.close().syncUninterruptibly(); serverGroup.shutdownGracefully(); } @Test void testWriteTimeout() { int writeTimeoutMillis = 100; String serverUrl = "http://localhost:" + ((InetSocketAddress) serverChannel.localAddress()).getPort(); // 配置WebClient,设置短写超时 HttpClient httpClient = HttpClient.create() .tcpConfiguration(client -> client .doOnConnected(conn -> conn .addHandlerLast(new WriteTimeoutHandler(writeTimeoutMillis)))); WebClient testWebClient = WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .baseUrl(serverUrl) .build(); // 发送一个大请求体,确保写操作持续足够长时间触发超时 String largeRequestBody = new String(new char[1024 * 1024]).replace('\0', 'a'); Mono<String> request = testWebClient.post() .bodyValue(largeRequestBody) .retrieve() .bodyToMono(String.class); StepVerifier.create(request) .expectError(WriteTimeoutException.class) .verify(); } }
原理:服务器接受连接后不读取数据,客户端发送大请求体时,数据会积压在发送缓冲区,超过WriteTimeoutHandler设置的时间后,Netty会抛出WriteTimeoutException,我们通过StepVerifier确认异常类型。
内容的提问来源于stack exchange,提问作者Anoop Hallimala
相关产品推荐
相关产品推荐

