In a recent job interview, I was asked to implement a fully fledged (that is,
including unit and integration tests) Lubang Menggali
(Mancala) game, where multiple users are
allowed to play in real-time. I went with Play
Framework and used
WebSockets to implement the real-time
server-client communication. (You can find the complete sources
here.) Play provides necessary leverage
for the integration tests – start the server, connect two browsers to it, click
on the buttons to make a move and check the board updates. However, the tricky
bit was the implementation of unit tests for WebSocket handlers.

Preliminaries

A typical WebSocket handler in Play has the following entry point for the
request dispatcher.

When a request hits to join(), the function returns a new WebSocket
instance, where onReady() will be invoked upon connection establishment. After
that, the book keeping of the input and output sockets are delegated to the
programmer. Note that the client-server communication is performed in JSON
messages in the above code snippet.

Mocking

In order to mock a client-server WebSocket communication, I need mock
WebSocket.In and WebSocket.Out class implementations. For that purpose, I
first tried googling alternatives and checked what other people have done
previously, but did not find much material. Hence, I came up with the following
MockInputWebSocket.

Here, I first implement a WebSocket.In object, where message and close event
listeners can register themselves to messageListeners and closeListeners
list, respectively. Next, in order to pass data to the socket listeners, I wrote
my custom write() and close() methods.

I also implemented MockOutputWebSocket similarly:

@ThreadSafeclassMockOutputWebSocket{privatefinalstaticObjectMapperobjectMapper=newObjectMapper();protectedfinalBlockingQueue<JsonNode>messageQueue=newLinkedBlockingQueue<>();protectedfinalWebSocket.Out<JsonNode>outputSocket=newWebSocket.Out<JsonNode>(){@Overridepublicvoidwrite(JsonNodeframe){messageQueue.add(frame);}@Overridepublicvoidclose(){try{messageQueue.add(objectMapper.readTree("{\"closed\": true}"));}// This should not happen.catch(IOExceptione){thrownewRuntimeException(e);}}};publicBlockingQueue<JsonNode>getMessageQueue(){returnmessageQueue;}publicWebSocket.Out<JsonNode>getOutputSocket(){returnoutputSocket;}}

If we would forget about the custom close message hack (nasty!) in close()
method of the implemented WebSocket.Out class, things are self-explanative
here as well. That is, when we receive a new message, we push it to
messageQueue. The test user will be able to consume the messages written to
the mock output socket by polling JsonData from the messageQueue.

Since I come this far, I also implemented an entire WebSocket mock
(MockWebSocket) that employs the aforementioned MockInputWebSocket and
MockOutputWebSocket as follows.

MockWebSocket provides the caller an interface such that the two-way
WebSocket communication can be intercepted through provided read(),
write() and close() methods – much like a regular network socket.

Conclusion

Remember that I used JSON as the messaging medium. For that purpose, I created a
couple of event classes (e.g., WaitingForOpponent, ReadyToStart,
IllegalMove, etc.) and used Jackson for
POJO-JSON (de)serialization. Next, I integrated my brand new MockWebSocket
into the unit tests as follows.

privatestatic<T>TreadPojo(MockWebSocketsocket,Class<T>clazz)throwsInterruptedException,JsonProcessingException{JsonNodedata=socket.read();assertThat(data).isNotNull();try{returnobjectMapper.convertValue(data,clazz);}catch(IllegalArgumentExceptioniae){thrownewIllegalArgumentException("Invalid JSON: "+objectMapper.writeValueAsString(data),iae);}}privatestaticvoidwriteMove(MockWebSocketsocket,Objectpit)throwsThrowable{socket.write(objectMapper.valueToTree(pit));}@TestpublicvoidtestJoin()throwsThrowable{// Introduce the first user and read the "WaitingForOpponent" message.MockWebSocketfstSocket=newMockWebSocket(Application.join());WaitingForOpponentfstWfo=readPojo(fstSocket,WaitingForOpponent.class);assertThat(Application.getPendingPlayers().size()).isEqualTo(1);assertThat(Application.getGames().size()).isEqualTo(0);// Introduce the second user and read the."WaitingForOpponent" message.MockWebSocketsndSocket=newMockWebSocket(Application.join());WaitingForOpponentsndWfo=readPojo(sndSocket,WaitingForOpponent.class);assertThat(fstWfo.playerId.equals(sndWfo.playerId)).isFalse();// Validate "ReadyToStart" messages.ReadyToStartfstRts=readPojo(fstSocket,ReadyToStart.class);ReadyToStartsndRts=readPojo(sndSocket,ReadyToStart.class);assertThat(Application.getGames().size()).isEqualTo(1);assertThat(Application.getPendingPlayers().size()).isEqualTo(0);assertThat(fstRts.nextPlayerId).isEqualTo(sndRts.nextPlayerId);assertThat(fstRts.opponentId).isEqualTo(sndWfo.playerId);assertThat(sndRts.opponentId).isEqualTo(fstWfo.playerId);// Let 2nd player make a move, while this is not his turn.writeMove(sndSocket,0);IllegalMoveim=readPojo(sndSocket,IllegalMove.class);assertThat(im.reason).isEqualTo("It is opponent's turn.");// Let 1st player make a move to an invalid pit index.writeMove(fstSocket,"n/a");im=readPojo(fstSocket,IllegalMove.class);assertThat(im.reason).matches("^Invalid pit index: .*");// Let 1st player make a move with a negative pit index.writeMove(fstSocket,-1);im=readPojo(fstSocket,IllegalMove.class);assertThat(im.reason).matches("^Invalid pit index: .*");// ...}

I suppose I have got a nearly 99% code coverage of the game engine by using the
mock WebSockets. I hope this scheme would help others trying to do a similar
testing stuff.