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