Spring Boot how to test Netty Handler?

huangapple go评论58阅读模式
英文:

Spring Boot how to test Netty Handler?

问题

I have a Netty TCP Server with Spring Boot 2.3.1.

I have been looking for many resources for testing handlers for Netty, like

However, it didn't work for me.

For EmbeddedChannel I have the following error - Your remote address is embedded.

Here is code:

@ActiveProfiles("test")
@RunWith(MockitoJUnitRunner.class)
public class ProcessingHandlerTest_Embedded {

    @Mock
    private PermissionService permissionService;
    private EmbeddedChannel embeddedChannel;
    private final Gson gson = new Gson();

    private ProcessingHandler processingHandler;

    @Before
    public void setUp() {
        processingHandler = new ProcessingHandler(permissionService);
        embeddedChannel = new EmbeddedChannel(processingHandler);
    }

    @Test
    public void testHeartbeatMessage() {
        // given
        HeartbeatRequest heartbeatMessage = HeartbeatRequest.builder()
                .messageID("heartbeat")
                .build();

        HeartbeatResponse response = HeartbeatResponse.builder()
                .responseCode("ok")
                .build();
        String request = gson.toJson(heartbeatMessage).concat("\r\n");
        String expected = gson.toJson(response).concat("\r\n");

        // when
        embeddedChannel.writeInbound(request);

        // then
        Queue<Object> outboundMessages = embeddedChannel.outboundMessages();
        assertEquals(expected, outboundMessages.poll());
    }
}

Output:

22:21:29.062 [main] INFO handler.ProcessingHandler - CLIENT_IP: embedded
22:21:29.062 [main] INFO handler.ProcessingHandler - CLIENT_REQUEST: {"messageID":"heartbeat"}

22:21:29.067 [main] DEBUG handler.ProcessingHandler - heartbeat request: HeartbeatRequest(messageID=heartbeat)

org.junit.ComparisonFailure: 
<Click to see difference>

I tried something with a remote address:

@ActiveProfiles("test")
@RunWith(MockitoJUnitRunner.class)
public class ProcessingHandlerTest {

    @Mock
    private PermissionService permissionService;
    private final Gson gson = new Gson();

    private ProcessingHandler processingHandler;

    @Mock
    private ChannelHandlerContext channelHandlerContext;

    @Mock
    private Channel channel;

    @Mock
    private SocketAddress remoteAddress;

    @Before
    public void setUp() {
        processingHandler = new ProcessingHandler(permissionService);
        when(channelHandlerContext.channel()).thenReturn(channel);
        when(channelHandlerContext.channel().remoteAddress()).thenReturn(remoteAddress);
    }

    @Test
    public void testHeartbeatMessage() {
        // given
        HeartbeatRequest heartbeatMessage = HeartbeatRequest.builder()
                .messageID("heartbeat")
                .build();

        HeartbeatResponse response = HeartbeatResponse.builder()
                .responseCode("ok")
                .build();
        String request = gson.toJson(heartbeatMessage).concat("\r\n");
        String expected = gson.toJson(response).concat("\r\n");

        // when
        processingHandler.channelRead(channelHandlerContext, request);

        // then
    }
}

Output:

22:26:06.119 [main] INFO handler.ProcessingHandler - CLIENT_IP: null
22:26:06.124 [main] INFO handler.ProcessingHandler - CLIENT_REQUEST: {"messageID":"heartbeat"}

22:26:06.127 [main] DEBUG handler.ProcessingHandler - heartbeat request: HeartbeatRequest(messageID=heartbeat)

However, I don't know how to do exact testing for such a case.

Here is a snippet from configuration:

```java
@Bean
@SneakyThrows
public InetSocketAddress tcpSocketAddress() {
    // for now, hostname is: localhost/127.0.0.1:9090
    return new InetSocketAddress("localhost", nettyProperties.getTcpPort());

    // for real client devices: A05264/172.28.1.162:9090
    // return new InetSocketAddress(InetAddress.getLocalHost(), nettyProperties.getTcpPort());
}

@Component
@RequiredArgsConstructor
public class QrReaderChannelInitializer extends ChannelInitializer<SocketChannel> {

    private final StringEncoder stringEncoder = new StringEncoder();
    private final StringDecoder stringDecoder = new StringDecoder();

    private final QrReaderProcessingHandler readerServerHandler;
    private final NettyProperties nettyProperties;

    @Override
    protected void initChannel(SocketChannel socketChannel) {
        ChannelPipeline pipeline = socketChannel.pipeline();

        // Add the text line codec combination first
        pipeline.addLast(new DelimiterBasedFrameDecoder(1024 * 1024, Delimiters.lineDelimiter()));

        pipeline.addLast(new ReadTimeoutHandler(nettyProperties.getClientTimeout()));
        pipeline.addLast(stringDecoder);
        pipeline.addLast(stringEncoder);
        pipeline.addLast(readerServerHandler);
    }
}

The handler is a typical implementation of ChannelInboundHandlerAdapter with overriding main methods.

How to test Handler with Spring Boot?


<details>
<summary>英文:</summary>

I have a Netty TCP Server with Spring Boot `2.3.1`.

I have been looking for many resources for testing handlers for Netty, like 

- [Testing Netty with EmbeddedChannel](https://www.baeldung.com/testing-netty-embedded-channel)
- [How to unit test netty handler](https://stackoverflow.com/questions/19312306/how-to-unit-test-netty-handler/34790002)

However, it didn&#39;t work for me.

For EmbeddedChannel I have following error - `Your remote address is embedded.`

Here is code:

    @ActiveProfiles(&quot;test&quot;)
    @RunWith(MockitoJUnitRunner.class)
    public class ProcessingHandlerTest_Embedded {
    
        @Mock
        private PermissionService permissionService;
        private EmbeddedChannel embeddedChannel;
        private final Gson gson = new Gson();
    
        private ProcessingHandler processingHandler;
    
    
        @Before
        public void setUp() {
            processingHandler = new ProcessingHandler(permissionService);
            embeddedChannel = new EmbeddedChannel(processingHandler);
        }
    
        @Test
        public void testHeartbeatMessage() {
            // given
            HeartbeatRequest heartbeatMessage = HeartbeatRequest.builder()
                    .messageID(&quot;heartbeat&quot;)
                    .build();
    
            HeartbeatResponse response = HeartbeatResponse.builder()
                    .responseCode(&quot;ok&quot;)
                    .build();
            String request = gson.toJson(heartbeatMessage).concat(&quot;\r\n&quot;);
            String expected = gson.toJson(response).concat(&quot;\r\n&quot;);
    
            // when
            embeddedChannel.writeInbound(request);
    
            // then
            Queue&lt;Object&gt; outboundMessages = embeddedChannel.outboundMessages();
            assertEquals(expected, outboundMessages.poll());
        }
    }

**Output:**

    22:21:29.062 [main] INFO handler.ProcessingHandler - CLIENT_IP: embedded
    22:21:29.062 [main] INFO handler.ProcessingHandler - CLIENT_REQUEST: {&quot;messageID&quot;:&quot;heartbeat&quot;}
    
    22:21:29.067 [main] DEBUG handler.ProcessingHandler - heartbeat request: HeartbeatRequest(messageID=heartbeat)
    
    org.junit.ComparisonFailure: 
    &lt;Click to see difference&gt;

[![enter image description here][1]][1]

I tried something with a remote address:

    @ActiveProfiles(&quot;test&quot;)
    @RunWith(MockitoJUnitRunner.class)
    public class ProcessingHandlerTest {
    
        @Mock
        private PermissionService permissionService;
        private final Gson gson = new Gson();
    
        private ProcessingHandler processingHandler;
    
        @Mock
        private ChannelHandlerContext channelHandlerContext;
    
        @Mock
        private Channel channel;
    
        @Mock
        private SocketAddress remoteAddress;
    
        @Before
        public void setUp() {
            processingHandler = new ProcessingHandler(permissionService);
            when(channelHandlerContext.channel()).thenReturn(channel);
            when(channelHandlerContext.channel().remoteAddress()).thenReturn(remoteAddress);
        }
    
        @Test
        public void testHeartbeatMessage() {
            // given
            HeartbeatRequest heartbeatMessage = HeartbeatRequest.builder()
                    .messageID(&quot;heartbeat&quot;)
                    .build();
    
            HeartbeatResponse response = HeartbeatResponse.builder()
                    .responseCode(&quot;ok&quot;)
                    .build();
            String request = gson.toJson(heartbeatMessage).concat(&quot;\r\n&quot;);
            String expected = gson.toJson(response).concat(&quot;\r\n&quot;);
    
            // when
            processingHandler.channelRead(channelHandlerContext, request);
    
            // then
        }
    }

**Output:**

    22:26:06.119 [main] INFO handler.ProcessingHandler - CLIENT_IP: null
    22:26:06.124 [main] INFO handler.ProcessingHandler - CLIENT_REQUEST: {&quot;messageID&quot;:&quot;heartbeat&quot;}
    
    22:26:06.127 [main] DEBUG handler.ProcessingHandler - heartbeat request: HeartbeatRequest(messageID=heartbeat)

However, I don&#39;t know how to do exact testing for such a case.

Here is a snippet from configuration:

    @Bean
    @SneakyThrows
    public InetSocketAddress tcpSocketAddress() {
        // for now, hostname is: localhost/127.0.0.1:9090
        return new InetSocketAddress(&quot;localhost&quot;, nettyProperties.getTcpPort());

        // for real client devices: A05264/172.28.1.162:9090
        // return new InetSocketAddress(InetAddress.getLocalHost(), nettyProperties.getTcpPort());
    }

    @Component
    @RequiredArgsConstructor
    public class QrReaderChannelInitializer extends ChannelInitializer&lt;SocketChannel&gt; {
    
        private final StringEncoder stringEncoder = new StringEncoder();
        private final StringDecoder stringDecoder = new StringDecoder();
    
        private final QrReaderProcessingHandler readerServerHandler;
        private final NettyProperties nettyProperties;
    
        @Override
        protected void initChannel(SocketChannel socketChannel) {
            ChannelPipeline pipeline = socketChannel.pipeline();
    
            // Add the text line codec combination first
            pipeline.addLast(new DelimiterBasedFrameDecoder(1024 * 1024, Delimiters.lineDelimiter()));
    
            pipeline.addLast(new ReadTimeoutHandler(nettyProperties.getClientTimeout()));
            pipeline.addLast(stringDecoder);
            pipeline.addLast(stringEncoder);
            pipeline.addLast(readerServerHandler);
        }
    }

The handler is a typical implementation of `ChannelInboundHandlerAdapter` with overriding main methods.

***How to test Handler with Spring Boot?***


  [1]: https://i.stack.imgur.com/XK0Ah.png

</details>


# 答案1
**得分**: 1

以下是翻译好的内容:

所以,`Your remote address is embedded.` 并不是一个错误,而是您正在收到的意外输出。

关于如何为Spring Boot编写Netty测试并没有特别之处。以下是一个最小化测试的示例,用于测试始终输出“ok”的处理程序:

```java
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.CharsetUtil;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest
class DemoApplicationTests {

    @Test
    void testRoundTrip() {
        // 给定
        String request = "heartbeat";

        // 当
        EmbeddedChannel embeddedChannel = new EmbeddedChannel(new OkResponder());
        embeddedChannel.writeInbound(request);

        // 然后
        ByteBuf outboundMessage = embeddedChannel.readOutbound();
        // (ByteBuf)embeddedChannel.outboundMessages().poll(),正如您在上面的行中使用的那样。
        assertEquals("ok", outboundMessage.toString(CharsetUtil.UTF_8));
    }

    static class OkResponder extends ChannelInboundHandlerAdapter {
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            ctx.writeAndFlush(Unpooled.copiedBuffer("ok", CharsetUtil.UTF_8))
                    .addListener(ChannelFutureListener.CLOSE);
        }
    }
}

我认为您的 ProcessingHandler 在某个时候调用了 ctx.channel().remoteAddress(),这会对于 EmbeddedChannel 返回 "embedded",这导致了您所看到的输出。

英文:

So Your remote address is embedded. isn't so much an error as it is, unexpected output which you're receiving.

There isn't anything special about how netty tests should be written for Spring Boot. As an example, here is a minimal test for a handler which always outputs "ok":

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.CharsetUtil;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest
class DemoApplicationTests {

    @Test
    void testRoundTrip() {
        // given
        String request = &quot;heartbeat&quot;;

        // when
        EmbeddedChannel embeddedChannel = new EmbeddedChannel(new OkResponder());
        embeddedChannel.writeInbound(request);

        // then
        ByteBuf outboundMessage = embeddedChannel.readOutbound();
        //(ByteBuf)embeddedChannel.outboundMessages().poll(), as you&#39;ve used, works in the above line too.
        assertEquals(&quot;ok&quot;, outboundMessage.toString(CharsetUtil.UTF_8));
    }

    static class OkResponder extends ChannelInboundHandlerAdapter {
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            ctx.writeAndFlush(Unpooled.copiedBuffer(&quot;ok&quot;, CharsetUtil.UTF_8))
                    .addListener(ChannelFutureListener.CLOSE);
        }
    }
}

It looks to me that your ProcessingHandler is calling ctx.channel().remoteAddress() at some point which will return "embedded" for an EmbeddedChannel and that is causing the output that you're seeing.

huangapple
  • 本文由 发表于 2020年7月31日 00:26:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/63177424.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定