Unit testing ChannelHandlers

Iain McGinniss iainmcgin at gmail.com
Mon Jul 27 16:42:36 EDT 2009


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


More information about the netty-users mailing list