On Mon, May 9, 2011 at 3:03 PM, Steve Atkinson <span dir="ltr"><<a href="mailto:steven.atkinson@kaazing.com">steven.atkinson@kaazing.com</a>></span> wrote:<br><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
Hi there.<br>
<br>
I'm writing some unit tests for channel handlers, and I am considering building fake channels, pipelines, an upstream product queue and a downstream channel buffer.<br>
<br>
I need to be able to send and receive custom events and assert over product queues and bytes written.<br>
<br>
Is there anything like this besides the CodecEmbedder - I'm planning to whip up something like that but for general channel handler testing not just codecs...<br></blockquote><div><br></div><div>I've had some success with unit testing channel handlers and custom events in pipelines.</div>
<div><br></div><div>For a channel handler, the basic technique is to mock the Channel and ChannelHandlerContext, then instantiate your handler with whatever dependencies it requires with mocks, and lastly, create a stub MessageEvent, containing the message your handler expects. Here's a small test example using JMock:</div>
<div><br></div><div><div>public class ExampleChannelHandlerTest {</div><div> </div><div> private Mockery context = new JUnit4Mockery() {</div><div> {</div><div> setImposteriser(ClassImposteriser.INSTANCE);</div>
<div> }</div><div> };</div><div><br></div><div> private ChannelHandlerContext mockCtx;</div><div> private Channel mockChannel;</div><div> private ChannelSender mockChannelSender;</div><div> private RemoteUpdateProvider mockUpdateProvider;</div>
<div> private MessagingHandler mockMessageProcessor;</div><div> private MyAppServerHandler mockMyAppHandler;</div><div> private RemoteServiceChannelHandler channelHandler;</div><div> private String clientIp = "172.0.0.1";</div>
<div> int protocolVersion = 1;</div><div><br></div><div> @Before</div><div> public void setup() {</div><div> mockCtx = context.mock(ChannelHandlerContext.class);</div><div> mockChannel = context.mock(Channel.class);</div>
<div> mockMyAppHandler = context.mock(MyAppServerHandler.class);</div><div> mockUpdateProvider = context.mock(RemoteUpdateProvider.class);</div><div> mockMessageProcessor = context.mock(MessagingHandler.class);</div>
<div> mockChannelSender = context.mock(ChannelSender.class);</div><div> channelHandler = new RemoteServiceChannelHandler(</div><div> mockUpdateProvider, mockMessageProcessor, mockChannelSender);</div>
<div> }</div><div><br></div><div> @Test</div><div> public void testPingEventHandlerOnReceiveSendsCorrectPingMessage() throws PacketException {</div><div> final long serial = 33333;</div><div> final int versionCode = 273843;</div>
<div> final int typeCode = 231111;</div><div> final UpdateResult result = UpdateResult.newBuilder()</div><div> .setStatus(UpdateResult.ResponseStatus.NO_RESULTS_INCLUDED)</div><div> .build();</div>
<div><br></div><div> VersionInfo vi = new VersionInfo(versionCode);</div><div> PacketPing ping = new PacketPing(serial, typeCode,</div><div> vi.major(), vi.minor(), vi.build(), protocolVersion);</div>
<div> PingEvent executionEvent = new PingEvent(ping, mockMyAppHandler, clientIp);</div><div><br></div><div> final PingMessage message = PingMessage.newBuilder()</div><div> .setRemoteIp(clientIp)</div>
<div> .setSerialNumber(ping.getSerialNumber())</div><div> .setVersionCode(ping.getVersionCode())</div><div> .setTypeCode(ping.getTypeCode())</div><div> .build();</div>
<div><br></div><div> context.checking(new Expectations() {</div><div> {</div><div> oneOf(mockMessageProcessor).onPingEvent(message);</div><div> oneOf(mockUpdateProvider).getFirmwareUpdate(serial,</div>
<div> versionCode, typeCode, "172.0.0.1");</div><div> will(returnValue(result));</div><div> oneOf(mockMyAppHandler).getProtocolVersion();</div><div> will(returnValue(protocolVersion));</div>
<div> oneOf(mockChannelSender).sendAndForget(mockCtx, mockChannel,</div><div> new PacketAck(ResponseCode.OK, protocolVersion));</div><div> oneOf(mockMyAppHandler).onResponseSent(</div>
<div> new PacketAck(ResponseCode.OK, protocolVersion));</div><div> oneOf(mockMessageProcessor).onPingEvent(message);</div><div> }</div><div> });</div><div> </div>
<div> MessageEvent e = getMessageStub(executionEvent);</div><div> channelHandler.messageReceived(mockCtx, e);</div><div> }</div><div> </div><div> private MessageEvent getMessageStub(final Object message) {</div>
<div> return new MessageEvent() {</div><div> @Override</div><div> public Object getMessage() {</div><div> return message;</div><div> }</div><div> @Override</div>
<div> public SocketAddress getRemoteAddress() {</div><div> return null;</div><div> }</div><div> @Override</div><div> public Channel getChannel() {</div><div> return mockChannel;</div>
<div> }</div><div> @Override</div><div> public ChannelFuture getFuture() {</div><div> return null;</div><div> }</div><div> };</div><div> }</div><div>}</div>
</div><div><div><br></div></div><div><meta http-equiv="content-type" content="text/html; charset=utf-8">RemoteServiceChannelHandler is an upstream ExecutionHandler. MyAppServerHandler, in this case, is a non-sharable handler that sits further downstream and deals with most of the client/server state. When it receives, a PingPacket from the client, it creates a PingEvent, which is sent upstream to the RemoteServiceChannelHandler (passing itself to provide callback for the execution handler). The other mocks represent a client interface for checking for remote updates, a channel writer helper, and an interface for JMS messaging. I've found it pretty easy to test various code paths of a handler using this technique.</div>
<div><br></div><div>Testing a pipeline is a bit messier. Here's a trimmed down example:</div><div><br></div><div><div><div>public class ExamplePipelineTest {</div><div> private static ExecutionHandler realExecutionHandler = new ExecutionHandler(</div>
<div> new OrderedMemoryAwareThreadPoolExecutor(4, 24288, 24288));</div><div><br></div><div> private Mockery context = new JUnit4Mockery() {</div><div> {</div><div> setImposteriser(ClassImposteriser.INSTANCE);</div>
<div> }</div><div> };</div><div> </div><div> /**</div><div> * Override these methods to return mocks to test expectations.</div><div> */</div><div> private class MyAppHandlerProviderStub implements ChannelHandlerProvider {</div>
<div> public PacketDecoder getPacketDecoder() {</div><div> return realDecoder;</div><div> }</div><div> public PacketEncoder getPacketEncoder() {</div><div> return realEncoder;</div>
<div> }</div><div> public ReadTimeoutHandler getReadTimeoutHandler() {</div><div> return readTimeoutHandler;</div><div> }</div><div> public MyAppServerHandler getMyAppServerHandler() {</div>
<div> return realServerHandler;</div><div> }</div><div> public ExecutionHandler getExecutionHandler() {</div><div> return realExecutionHandler;</div><div> }</div><div> public RemoteServiceChannelHandler getRemoteServiceChannelHandler() {</div>
<div> return realRemoteServiceHandler;</div><div> }</div><div> public RemoteProgressHandler getRemoteProgressHandler() {</div><div> return realRemoteProgressHandler;</div><div> }</div>
<div> }</div><div><br></div><div> // mocks</div><div> private RemoteUpdateProvider mockRemoteUpdateProvider;</div><div> private MessagingHandler mockMessageProcessor;</div><div> private Channel mockChannel;</div>
<div> private ChannelSink mockSink;</div><div> private ChannelSender mockChannelSender;</div><div><br></div><div> // real implementations</div><div> private String clientIp = "172.0.0.1";</div><div> int protocolVersion = 1;</div>
<div> private PacketEncoder realEncoder;</div><div> private PacketDecoder realDecoder;</div><div> private ReadTimeoutHandler readTimeoutHandler;</div><div> private MyAppServerHandler realServerHandler;</div><div>
<br></div><div> private RemoteServiceChannelHandler realRemoteServiceHandler;</div><div> private RemoteProgressHandler realRemoteProgressHandler;</div><div> private ChannelGroupHandler realChannelGroupHandler;</div>
<div> private MyAppPipelineFactory pipelineFactory;</div><div> private ChannelPipeline pipeline;</div><div> private static Timer timer = new HashedWheelTimer();</div><div> private ChannelConfig channelConfig = new DefaultServerChannelConfig();</div>
<div><br></div><div> @AfterClass</div><div> public static void stopTimer() {</div><div> timer.stop();</div><div> realExecutionHandler.releaseExternalResources();</div><div> }</div><div><br></div><div>
@Before</div><div> public void setup() throws Exception {</div><div> realEncoder = new PacketEncoder();</div><div> realDecoder = new PacketDecoder();</div><div> readTimeoutHandler = new ReadTimeoutHandler(timer, 30);</div>
<div> mockChannelSender = context.mock(ChannelSender.class);</div><div> realServerHandler = new MyAppServerHandler(</div><div> new UpstreamEventDispatcher(), mockChannelSender);</div><div> realServerHandler.setRemoteIp(clientIp);</div>
<div><br></div><div> mockRemoteUpdateProvider = context.mock(RemoteUpdateProvider.class);</div><div> mockMessageProcessor = context.mock(MessagingHandler.class);</div><div><br></div><div> realRemoteServiceHandler = new RemoteServiceChannelHandler(</div>
<div> mockRemoteUpdateProvider, mockMessageProcessor,</div><div> mockChannelSender);</div><div><br></div><div> realRemoteProgressHandler = new RemoteProgressHandler(</div><div> mockMessageProcessor, mockChannelSender);</div>
<div><br></div><div> mockChannel = context.mock(Channel.class);</div><div> mockSink = context.mock(ChannelSink.class);</div><div><br></div><div> pipelineFactory = new MyAppPipelineFactory(</div><div> new MyAppHandlerProviderStub());</div>
<div> }</div><div><br></div><div> @Test</div><div> public void testPipelineOnPing() throws Exception {</div><div> pipeline = pipelineFactory.getPipeline();</div><div> pipeline.attach(mockChannel, mockSink);</div>
<div><br></div><div> context.checking(new Expectations() {</div><div> {</div><div> oneOf(mockChannel).getConfig();</div><div> will(returnValue(channelConfig));</div><div> exactly(3).of(mockChannel).getRemoteAddress();</div>
<div> will(returnValue(null));</div><div><br></div><div> exactly(3).of(mockChannel).isOpen();</div><div> will(returnValue(true));</div><div> oneOf(mockChannel).isReadable();</div>
<div> will(returnValue(true));</div><div><br></div><div> // finally passed to upstream handler</div><div> oneOf(mockMessageProcessor).onPingEvent(</div><div> with(any(PingMessageProto.PingMessage.class)));</div>
<div> oneOf(mockRemoteUpdateProvider).getFirmwareUpdate(1882993,</div><div> 65536, 1, clientIp);</div><div> // stub no results</div><div> will(returnValue(UpdateResultProto.UpdateResult</div>
<div> .newBuilder()</div><div> .setStatus(</div><div> UpdateResultProto.UpdateResult.ResponseStatus.NO_RESULTS_INCLUDED)</div><div> .build()));</div>
<div> }</div><div> });</div><div><br></div><div> byte[] pingMessage = new byte[] { ... <bytes> ... };</div><div> triggerMessageEvent(pipeline, ChannelBuffers.wrappedBuffer(pingMessage));</div>
<div> waitForExecutorThread();</div><div> }</div><div> </div><div> private void waitForExecutorThread() throws InterruptedException {</div><div> // test will fail if expectations are evaluated before executor thread</div>
<div> // returns, therefore we just sleep for a moment here.</div><div> Thread.sleep(50);</div><div> }</div><div> </div><div> private void triggerMessageEvent(</div><div> ChannelPipeline cp, ChannelBuffer cb) throws Exception {</div>
<div> UpstreamMessageEvent upMessage = new UpstreamMessageEvent(mockChannel, cb, null);</div><div> ChannelHandlerContext decoderCtx = cp.getContext("decoder");</div><div> PacketDecoder decoder = (PacketDecoder) decoderCtx.getHandler();</div>
<div> decoder.handleUpstream(decoderCtx, upMessage);</div><div> }</div><div>}</div></div></div><div><br></div><div><br></div><div>A stubbed pipeline factory is created with handlers that have mocked dependencies. triggerMessageEvent() starts the test by calling handleUpstream() on my decoder. Then the pipeline interactions are verified by checking the expectations on the mocks. Using something like Mockito instead of JMock could really help with the verbosity of the expectations in this case, but it was interesting to see everything that was happening.</div>
<div><br></div><div>It was kind of a trial an error process setting these up (especially the pipeline test), and it's pretty messy, but it might help you get started in the direction you're going.</div><div><br></div>
<div>Jesse</div><div><br></div></div>