英文:
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't work for me.
For EmbeddedChannel I have 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>
[![enter image description here][1]][1]
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:
@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?***
[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 = "heartbeat";
// when
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new OkResponder());
embeddedChannel.writeInbound(request);
// then
ByteBuf outboundMessage = embeddedChannel.readOutbound();
//(ByteBuf)embeddedChannel.outboundMessages().poll(), as you've used, works in the above line too.
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);
}
}
}
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论