Unit testing ChannelHandlers

Iain McGinniss iainmcgin at gmail.com
Thu Aug 6 03:50:34 EDT 2009


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>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
> > 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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.jboss.org/pipermail/netty-users/attachments/20090806/161ac58c/attachment.html 


More information about the netty-users mailing list