catch32
catch32

Reputation: 18592

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 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

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?

Upvotes: 0

Views: 2453

Answers (1)

CamW
CamW

Reputation: 3363

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.

Upvotes: 1

Related Questions