1. 简介
本文将介绍如何使用 EmbeddedChannel 测试入站和出站通道处理器的功能。
Netty 是一个用于构建高性能异步应用的强大框架。但如果没有合适的工具,单元测试这类应用可能会很棘手。
幸运的是,框架提供了 EmbeddedChannel 类——专门用于简化 ChannelHandlers 的测试。
2. 环境准备
EmbeddedChannel 是 Netty 框架的一部分,所以只需要添加 Netty 的依赖即可。
该依赖可在 Maven Central 获取:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.24.Final</version>
</dependency>
3. EmbeddedChannel 核心机制
EmbeddedChannel 本质上是 AbstractChannel 的另一种实现——它无需真实网络连接即可传输数据。
这很有用,因为我们可以通过向入站通道写入数据来模拟入站消息,同时检查出站通道生成的响应。这样既能单独测试每个 ChannelHandler,也能测试整个通道管道。
要测试一个或多个 ChannelHandler,首先需要通过构造函数创建 EmbeddedChannel 实例。
最常用的初始化方式是直接传入 ChannelHandlers 列表:
EmbeddedChannel channel = new EmbeddedChannel(
new HttpMessageHandler(), new CalculatorOperationHandler());
如果想更灵活地控制处理器添加顺序,可以使用默认构造函数创建后手动添加:
channel.pipeline()
.addFirst(new HttpMessageHandler())
.addLast(new CalculatorOperationHandler());
另外,创建 EmbeddedChannel 时会使用 DefaultChannelConfig 提供的默认配置。
当需要自定义配置(比如降低连接超时时间)时,可通过 config() 方法获取配置对象:
DefaultChannelConfig channelConfig = (DefaultChannelConfig) channel
.config();
channelConfig.setConnectTimeoutMillis(500);
EmbeddedChannel 提供了读写 ChannelPipeline 数据的核心方法:
- readInbound()
- readOutbound()
- writeInbound(Object… msgs)
- writeOutbound(Object… msgs)
read 方法会移除并返回入站/出站队列的首个元素。当需要访问完整消息队列而不移除元素时,可使用 outboundMessages() 方法:
Object lastOutboundMessage = channel.readOutbound();
Queue<Object> allOutboundMessages = channel.outboundMessages();
*write 方法在消息成功添加到通道的入站/出站管道时返回 true:*
channel.writeInbound(httpRequest)
核心思路是:向入站管道写入消息,由 ChannelHandlers 处理后,从出站管道读取预期结果。
4. 测试 ChannelHandlers
来看一个简单示例:测试由两个处理器组成的管道,它们接收 HTTP 请求并返回包含计算结果的 HTTP 响应:
EmbeddedChannel channel = new EmbeddedChannel(
new HttpMessageHandler(), new CalculatorOperationHandler());
第一个处理器 HttpMessageHandler 从 HTTP 请求中提取数据,传递给管道中的第二个处理器 CalculatorOperationHandler 进行处理。
现在写入 HTTP 请求,验证入站管道的处理逻辑:
FullHttpRequest httpRequest = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET, "/calculate?a=10&b=5");
httpRequest.headers().add("Operator", "Add");
assertThat(channel.writeInbound(httpRequest)).isTrue();
long inboundChannelResponse = channel.readInbound();
assertThat(inboundChannelResponse).isEqualTo(15);
我们通过 writeInbound() 发送请求,用 readInbound() 读取结果。inboundChannelResponse 是数据经过所有入站处理器处理后的消息。
接下来验证服务器是否返回了正确的 HTTP 响应。检查出站管道是否存在消息:
assertThat(channel.outboundMessages().size()).isEqualTo(1);
出站消息是 HTTP 响应,验证其内容是否正确。读取出站管道的最后一条消息:
FullHttpResponse httpResponse = channel.readOutbound();
String httpResponseContent = httpResponse.content()
.toString(Charset.defaultCharset());
assertThat(httpResponseContent).isEqualTo("15");
5. 测试异常处理
另一个常见测试场景是异常处理。
在 ChannelInboundHandlers 中可通过实现 exceptionCaught() 方法处理异常,但有时我们希望将异常传递给管道中的下一个处理器。
使用 EmbeddedChannel 的 checkException() 方法可以检查管道中是否存在 Throwable 对象并重新抛出。
这样就能捕获异常并验证处理器是否按预期抛出:
assertThatThrownBy(() -> {
channel.pipeline().fireChannelRead(wrongHttpRequest);
channel.checkException();
}).isInstanceOf(UnsupportedOperationException.class)
.hasMessage("HTTP method not supported");
示例中我们发送了一个预期会触发异常的 HTTP 请求。通过 checkException() 重新抛出管道中的最后一个异常,即可进行断言验证。
6. 总结
EmbeddedChannel 是 Netty 框架提供的强大工具,用于验证 ChannelHandler 管道的正确性。它既能单独测试每个处理器,更重要的是能测试整个管道。
本文源码可在 GitHub 获取。