Testing Netty with EmbeddedChannel – 用EmbeddedChannel测试Netty

最后修改: 2018年 4月 23日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this article, we’ll see how to use EmbeddedChannel to test the functionality of our inbound and outbound channel handlers.

在这篇文章中,我们将看到如何使用EmbeddedChannel来测试我们的入站和出站通道处理程序的功能。

Netty is a very versatile framework for writing high-performance asynchronous applications. Unit testing such applications can be tricky without the right tools.

Netty是一个非常通用的框架,用于编写高性能异步应用程序。如果没有合适的工具,对此类应用程序进行单元测试是很棘手的。

Thankfully the framework provides us with the EmbeddedChannel class – which facilitates the testing of ChannelHandlers.

值得庆幸的是,框架为我们提供了EmbeddedChannel类–它方便了对ChannelHandlers的测试。

2. Setup

2.设置

The EmbeddedChannelis part of the Netty framework, so the only dependency needed is the one for Netty itself.

EmbeddedChannel是Netty框架的一部分,所以唯一需要依赖的是Netty本身。

The dependency can be found over on Maven Central:

可以在Maven Central上找到该依赖性。

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.24.Final</version>
</dependency>

3. EmbeddedChannelOverview

3.EmbeddedChannelOverview

The EmbeddedChannelclass is just another implementation of AbstractChannel – which transports data without the need for a real network connection.

EmbeddedChannel类只是AbstractChannel的另一种实现,它传输数据时不需要真正的网络连接

This is useful because we can simulate incoming messages by writing data on the inbound channels and also check the generated response on the outbound channels. This way we can individually test each ChannelHandler or in the whole channel pipeline.

这很有用,因为我们可以通过在入站通道上写数据来模拟传入的消息,也可以在出站通道上检查生成的响应。这样,我们可以单独测试每个ChannelHandler 或整个通道管道。

To test one or more ChannelHandlers, we first have to create an EmbeddedChannel instance using one of its constructors.

要测试一个或多个ChannelHandlers我们首先要使用其构造函数创建一个EmbeddedChannelinstance。

The most common way to initialize an EmbeddedChannel is by passing the list of ChannelHandlers to its constructor:

初始化EmbeddedChannel的最常见方式是将ChannelHandlers的列表传递给其构造函数:

EmbeddedChannel channel = new EmbeddedChannel(
  new HttpMessageHandler(), new CalculatorOperationHandler());

If we want to have more control on the order the handlers are inserted into the pipeline we can create an EmbeddedChannel with the default constructor and directly add the handlers:

如果我们想对处理程序插入管道的顺序有更多的控制,我们可以用默认的构造函数创建一个EmbeddedChannel并直接添加处理程序。

channel.pipeline()
  .addFirst(new HttpMessageHandler())
  .addLast(new CalculatorOperationHandler());

Also, when we create an EmbeddedChannel, it’ll have a default configuration given by the DefaultChannelConfigclass.

此外,当我们创建一个EmbeddedChannel时,它将有一个由DefaultChannelConfig类给出的默认配置。

When we want to use a custom configuration, like lowering the connect timeout value from the default one, we can access the ChannelConfig object by using the config()method:

当我们想使用一个自定义配置时,比如从默认值中降低连接超时值,我们可以通过使用config()方法访问ChannelConfig对象。

DefaultChannelConfig channelConfig = (DefaultChannelConfig) channel
  .config();
channelConfig.setConnectTimeoutMillis(500);

The EmbeddedChannel includes methods that we can use to read and write data to our ChannelPipeline. The most commonly used methods are:

EmbeddedChannel包括一些方法,我们可以使用这些方法来读写数据到我们的ChannelPipeline。最常用的方法是。

  • readInbound()
  • readOutbound()
  • writeInbound(Object… msgs)
  • writeOutbound(Object… msgs)

The read methods retrieve and remove the first element in the inbound/outbound queue.When we need access to the whole queue of messages without removing any element, we can use the outboundMessages() method:

读取方法检索并删除入站/出站队列中的第一个元素。当我们需要访问整个消息队列而不删除任何元素时,我们可以使用outboundMessages() 方法。

Object lastOutboundMessage = channel.readOutbound();
Queue<Object> allOutboundMessages = channel.outboundMessages();

The write methods return true when the message was successfully added to the inbound/outbound pipeline of the Channel:

当消息被成功添加到通道的入站/出站管道时,写入方法返回true

channel.writeInbound(httpRequest)

The idea is that we write messages on the inbound pipeline so that the out ChannelHandlerswill process them and we expect the result to be readable from the outbound pipeline.

这个想法是,我们在入站管道上写消息,以便出站ChannelHandlers将处理它们,我们期望结果可以从出站管道上读到。

4. Testing ChannelHandlers

4.测试ChannelHandlers

Let’s look at a simple example in which we want to test a pipeline composed of two ChannelHandlers that receive an HTTP request and expect an HTTP response that contains the result of a calculation:

让我们看一个简单的例子,我们想测试一个由两个ChannelHandlers组成的管道,它们接收一个HTTP请求并期待一个包含计算结果的HTTP响应。

EmbeddedChannel channel = new EmbeddedChannel(
  new HttpMessageHandler(), new CalculatorOperationHandler());

The first one, HttpMessageHandler will extract the data from the HTTP request and pass it to the seconds ChannelHandler in the pipeline, CalculatorOperationHandler, to do processing with the data.

第一个,HttpMessageHandler将从HTTP请求中提取数据,并将其传递给管道中的第二个ChannelHandlerCalculatorOperationHandler,以对数据进行处理。

Now, let’s write the HTTP request and see if the inbound pipeline processes it:

现在,让我们编写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);

We can see that we’ve sent the HTTP request on the inbound pipeline using the writeInbound() method and read the result with readInbound(); inboundChannelResponse is the message that resulted from the data we’ve sent after it was processed by all the ChannelHandlers in the inbound pipeline.

我们可以看到,我们用writeInbound()方法在入站管道上发送了HTTP请求,并用readInbound()读取了结果;inboundChannelResponse是我们发送的数据被入站管道中的所有ChannelHandlers处理后的信息。

Now, let’s check if our Netty server responds with the correct HTTP response message. To do this, we’ll check if a message exists on the outbound pipeline:

现在,让我们检查一下我们的Netty服务器是否回应了正确的HTTP响应信息。要做到这一点,我们要检查在出站管道上是否存在一个消息。

assertThat(channel.outboundMessages().size()).isEqualTo(1);

The outbound message, in this case, is an HTTP response, so let’s check if the content is correct. We do this, by reading the last message in the outbound pipeline:

在这种情况下,出站消息是一个HTTP响应,所以让我们检查一下内容是否正确。我们通过读取出站管道中的最后一条消息来做到这一点。

FullHttpResponse httpResponse = channel.readOutbound();
String httpResponseContent = httpResponse.content()
  .toString(Charset.defaultCharset());
assertThat(httpResponseContent).isEqualTo("15");

4. Testing Exception Handling

4.测试异常处理

Another common testing scenario is exception handling.

另一个常见的测试场景是异常处理。

We can handle exceptions in our ChannelInboundHandlers by implementing the exceptionCaught() method, but there’re some cases when we don’t want to handle an exception and instead, we pass it to the next ChannelHandlerin the pipeline.

我们可以通过实现exceptionCaught()方法在我们的ChannelInboundHandlers中处理异常,但是在某些情况下,我们不想处理异常,而是将其传递给管道中的下一个ChannelHandler

We can use the checkException() method from the EmbeddedChannelclass to check if any Throwable object was received on the pipeline and rethrows it.

我们可以使用EmbeddedChannel类中的checkException()方法来检查管道上是否收到任何Throwable对象并重新抛出。

This way we can catch the Exception and check whether the ChannelHandlershould or shouldn’t have thrown it:

这样我们就可以捕捉到Exception,并检查ChannelHandler是否应该抛出它。

assertThatThrownBy(() -> {
    channel.pipeline().fireChannelRead(wrongHttpRequest);
    channel.checkException();
}).isInstanceOf(UnsupportedOperationException.class)
  .hasMessage("HTTP method not supported");

We can see in the example above, that we’ve sent an HTTP request that we expect to trigger an Exception. By using the checkException() method we can rethrow the last exception that exists in the pipeline, so we can assert what is needed from it.

在上面的例子中,我们可以看到,我们已经发送了一个HTTP请求,预计会触发一个Exception。通过使用checkException() 方法,我们可以重新抛出管道中存在的最后一个异常,因此我们可以断言它需要什么。

5. Conclusion

5.总结

The EmbeddedChannel is a great feature provided by the Netty framework to help us test the correctness of out ChannelHandler pipeline. It can be used to test each ChannelHandler individually and more importantly the whole pipeline.

EmbeddedChannel是Netty框架提供的一个伟大的功能,帮助我们测试ChannelHandler管道的正确性。它可以用来测试每个ChannelHandler单独的,更重要的是整个管道。

The source code for the article is available over on GitHub.

文章的源代码可在GitHub上获得