Unit testing ChannelHandlers

"이희승 (Trustin Lee)" trustin at gmail.com
Thu Aug 6 03:56:03 EDT 2009


Yes, that's what exactly I did in the codec embedder.  Nice work!

Trustin

On 08/06/2009 04:50 PM, Iain McGinniss wrote:
> In the end, this is what I did. I created a FakeChannel extending
> AbstractChannel, with a FakeChannelSink that simply captured all events
> sent downstream in a queue. At the other end of the pipeline I created a
> simple upstream handler that captured all upstream events to a queue.
> This then allowed me to test channel handlers which selectively
> forwarded events up and down the stack, and the order in which events
> were forwarded, without getting tied up in mocking multiple objects
> which returned other mocks (i.e. ChannelContextHandler returning
> Channel, etc).
> 
> Iain
> 
> On Thu, Aug 6, 2009 at 7:12 AM, "이희승 (Trustin Lee)"
> <trustin at gmail.com <mailto:trustin at gmail.com>> wrote:
> 
>     Hi Iain,
> 
>     Because Channel or other interfaces have many operations and hold
>     somewhat complicated state, it will not be easy to implement a mock with
>     jMock or EasyMock.  Instead, I'd recommend to extending an 'Abstract*'
>     classes in Netty.  You might want to refer to the codec embedder source
>     code, which simplifies the unit testing of codec handlers.
> 
>     HTH,
>     Trustin
> 
>     On 07/28/2009 05:42 AM, Iain McGinniss wrote:
>     > Hi guys,
>     >
>     > I've been trying to implement the binary protocol used in JXTA using
>     > Netty for the past week or so (mixed in with trying to implement
>     an HTTP
>     > tunnel that doesn't use servlets, but that's parked for the
>     moment). In
>     > doing this I've been trying to unit test my channel handlers, which I
>     > have found to be quite challenging. I'm starting to wonder whether
>     this
>     > is because I am taking the wrong approach. First, I'll give a brief
>     > overview of the protocol:
>     >
>     > 1. When a connection is opened, I must send a "welcome" message. This
>     > contains some dynamic data used to identify myself, such as a
>     unique ID,
>     > the version of the protocol I am using and some meta-data about
>     how the
>     > connection should be used.
>     > 2. While this is happening, the peer on the other end will be sending
>     > their welcome message. This message ends with CRLF.
>     > 3. If I don't receive a full welcome message from the other peer
>     within
>     > 5 seconds, or if it is invalid in some way (wrong protocol version,
>     > unsupported modes specified in the meta-data, etc), then I must close
>     > the connection.
>     > 4. I cannot send any messages to the other peer until I have
>     received a
>     > full, valid welcome message from them.
>     > 5. Once this welcome message handshake is complete, I can send
>     messages
>     > to the peer. These messages are structured somewhat like HTTP
>     requests,
>     > except in binary with known binary keys mapping to known binary
>     values.
>     > One of these "headers" specifies the length of the rest of the
>     message,
>     > so as a receiver I can predict how long the message is, preallocate a
>     > buffer of the correct size, read the message and then process it.
>     > 6. Either peer can close the connection at the end of any message,
>     > without any kind of "goodbye" message required.
>     >
>     > In order to deal with this protocol I've created a ChannelHandler,
>     > extending SimpleChannelHandler.
>     >
>     > 1. I override the channelConnected() method in order to send my
>     welcome
>     > message, and I do not allow the connected event to bubble up the
>     > pipeline at this point.
>     > 2. I override the messageReceived() method to process the welcome
>     > message, and call channel.close() within this if the welcome
>     message is
>     > invalid or not what I expect. Once I receive a full welcome message, I
>     > fire my own UpstreamChannelStateEvent to state we are connected
>     > (properly). I actually don't know if this is "legal" or best practice.
>     > 3. I override the writeRequested() method to perform the framing
>     of the
>     > ChannelBuffers containing full messages passed down from the previous
>     > handler which has done the serialization.
>     >
>     > Now, unit testing this has proved challenging. For instance, take the
>     > simple unit test of "when channelConnected() is called, write a
>     welcome
>     > message to the channel". The implementation of channelConnected is:
>     >
>     > public void channelConnected(ChannelHandlerContext ctx,
>     > ChannelStateEvent e) throws Exception {
>     >     SocketAddress remoteAddress = ctx.getChannel().getRemoteAddress();
>     >     EndpointAddress destAddr = new
>     > EndpointAddress(endpointAddr.getProtocolName(),
>     > remoteAddress.toString(), null, null);
>     >     WelcomeMessage welcome = new WelcomeMessage(destAddr,
>     endpointAddr,
>     > peerId, false);
>     >     ChannelBuffer welcomeBytes =
>     > ChannelBuffers.copiedBuffer(welcome.getByteBuffer());
>     >     Channels.write(ctx, Channels.future(ctx.getChannel()),
>     welcomeBytes);
>     > }
>     >
>     > My approach so far to unit testing this has been to use jMock 2,
>     > producing tests blocks like this:
>     >
>     > @Test
>     > public void testSendsWelcomeMessageImmediately() throws Exception {
>     >     final UpstreamChannelStateEvent ev = new
>     > UpstreamChannelStateEvent(channel, ChannelState.CONNECTED, true);
>     >     final WelcomeMessage expectedWelcomeMessage = new
>     > WelcomeMessage(REMOTE_ENDPOINT_ADDR, LOCAL_ENDPOINT_ADDR,
>     LOCAL_PEER_ID,
>     > false);
>     >
>     >     mockContext.checking(new Expectations() {{
>     >         ignoring(ctx).getChannel(); will(returnValue(channel));
>     >         atLeast(1).of(channel).getRemoteAddress();
>     > will(returnValue(REMOTE_SOCK_ADDR));
>     >
>     >
>      one(ctx).sendDownstream(with(aDownstreamMessageEvent(aWelcomeMessage(expectedWelcomeMessage))));
>     >     }});
>     >
>     >     handler.channelConnected(ctx, ev);
>     > }
>     >
>     > The real sticking point was the "aDownstreamMessageEvent" and
>     > "aWelcomeMessage" Matchers I had to write by hand. The various event
>     > types in Netty do not implement equals() so I cannot do something
>     like:
>     >
>     > DownstreamMessageEvent expectedEvent = new
>     DownstreamMessageEvent(...);
>     > one(ctx).sendDownstream(with(equals(expectedDownStreamMessage));
>     >
>     > Additionally, in other test cases where I want to assert that my code
>     > fires events like an UpstreamChannelStateEvent with State=CONNECTED,
>     > Value=TRUE, I can't do this using equals() either. Instead, I must
>     write
>     > custom matchers to compensate for this. All this results in, for a
>     > simple method like channelConnected(), roughly 5-6 times the amount of
>     > code required to write a test compared to the code itself
>     (aggravated by
>     > most aspects of JXTA being difficult to test too, and using NIO
>     > ByteBuffers rather than ChannelBuffers... sigh). This will get easier
>     > over time as I start building more matchers for jmock specifically for
>     > Netty, but I still can't help feeling that there must be an easier way
>     > to test ChannelHandlers in isolation.
>     >
>     > Do you guys have any advice on how best to test Netty code?
>     > Iain
>     >
>     >
>     >
>     ------------------------------------------------------------------------
>     >
>     > _______________________________________________
>     > netty-users mailing list
>     > netty-users at lists.jboss.org <mailto:netty-users at lists.jboss.org>
>     > https://lists.jboss.org/mailman/listinfo/netty-users
> 
>     _______________________________________________
>     netty-users mailing list
>     netty-users at lists.jboss.org <mailto:netty-users at lists.jboss.org>
>     https://lists.jboss.org/mailman/listinfo/netty-users
> 
> 
> 
> ------------------------------------------------------------------------
> 
> _______________________________________________
> netty-users mailing list
> netty-users at lists.jboss.org
> https://lists.jboss.org/mailman/listinfo/netty-users



More information about the netty-users mailing list