Liam Kaufman

Software Developer and Entrepreneur

Testing Socket.IO With Mocha, Should.js and Socket.IO Client

I’m currently in the midst of creating an application that utilizes Socket.IO for real-time communication between users. Using mocha and should.js I was able to test objects within my node app. However, I quickly found that there were some odd synchronization issues between clients that the test cases couldn’t cover. To test the interaction between clients I needed a way to programatically communicate to my node server: Enter socket.io-client.

Testing Socket.IO on Github

Writing Tests For your Socket.io Application

As an example of how to test your socket.io application we’ll create a small chat application and test it using: socket.io-client, mocha and should.js.

Our chat server should be able to:

  1. broadcast that a new user has joined
  2. broadcast messages to the whole group
  3. handle private messages between clients

If you hit any roadblocks see Trouble shooting at the bottom of this post.

Without further ado, below is the bare essentials for our socket.IO chat server (chat-server.js) and our tests (tests/test-chat-server.js).

Server V0.1
1
2
3
4
5
var io = require('socket.io').listen(5000);

io.sockets.on('connection', function (socket) {

});
Tests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var should = require('should');
var io = require('socket.io-client');

var socketURL = 'http://0.0.0.0:5000';

var options ={
  transports: ['websocket'],
  'force new connection': true
};

var chatUser1 = {'name':'Tom'};
var chatUser2 = {'name':'Sally'};
var chatUser3 = {'name':'Dana'};

describe("Chat Server",function(){

});

First Test

Within the describe function (line 16 in tests) we will add our first test that determines if our chat server notifies all users that a new user has joined. It’s important to disconnect both clients once this test is over, otherwise the second test will trigger this test’s ‘new user’ event.

Test 1 - One user connects, both users should be notified.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
it('Should broadcast new user to all users', function(done){
  var client1 = io.connect(socketURL, options);

  client1.on('connect', function(data){
    client1.emit('connection name', chatUser1);

    /* Since first client is connected, we connect
    the second client. */
    var client2 = io.connect(socketURL, options);

    client2.on('connect', function(data){
      client2.emit('connection name', chatUser2);
    });

    client2.on('new user', function(usersName){
      usersName.should.equal(chatUser2.name + " has joined.");
      client2.disconnect();
    });

  });

  var numUsers = 0;
  client1.on('new user', function(usersName){
    numUsers += 1;

    if(numUsers === 2){
      usersName.should.equal(chatUser2.name + " has joined.");
      client1.disconnect();
      done();
    }
  });
});

Now that our first test is complete, let’s run the server.

Run the server
1
node chat-server.js

In another terminal window we can run the tests.

Run the tests
1
mocha -R spec

And both tests will fail (times out to be specific)! To pass the tests we simply update the chat-server.js to the following:

Server V0.2
1
2
3
4
5
6
7
8
var io = require('socket.io').listen(5000);

io.sockets.on('connection', function (socket) {
  socket.on('connection name',function(user){
    io.sockets.emit('new user', user.name + " has joined.");
  })

});

When you run the tests again they should both pass.

Second Test

Now, we’ll add the second test that determines if a message sent from one client is sent to all the clients connected to the chat server. It may not initially be obvious why I’m using 3 clients, but it should be in the 3rd test. To determine if all 3 clients have received a message we keep a counter (messges) and check that each received message is the correct message.

Test 2 - User sends a message to chat room
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
it('Should be able to broadcast messages', function(done){
  var client1, client2, client3;
  var message = 'Hello World';
  var messages = 0;

  var checkMessage = function(client){
    client.on('message', function(msg){
      message.should.equal(msg);
      client.disconnect();
      messages++;
      if(messages === 3){
        done();
      };
    });
  };

  client1 = io.connect(socketURL, options);
  checkMessage(client1);

  client1.on('connect', function(data){
    client2 = io.connect(socketURL, options);
    checkMessage(client2);

    client2.on('connect', function(data){
      client3 = io.connect(socketURL, options);
      checkMessage(client3);

      client3.on('connect', function(data){
        client2.send(message);
      });
    });
  });
});

If you run the tests again it should fail! We can fix this by updating the server code to look like:

Server V0.3
1
2
3
4
5
6
7
8
9
10
11
var io = require('socket.io').listen(5000);

io.sockets.on('connection', function (socket) {
  socket.on('connection name',function(user){
    io.sockets.emit('new user', user.name + " has joined.");
  });

  socket.on('message', function(msg){
    io.sockets.emit('message', msg);
  });
});

Third Test

With the first two bits of functionality complete, and tested, we now need to test that 1) a client can send a private message to another client and 2) no other clients receive that message. At minimum we need 3 clients to test this condition. Once all 3 clients have connected, the 3rd client will send a private message to the first client. It’s easy to test that the first client has received the message, but how do we test that the 2nd client has not? The solution I’m using below is that the test simply waits 40 milliseconds and if only 1 message has been received, there’s a strong chance that client 2 has not received a message. I’m not sure how to write the test to be 100% sure that the client has not received the message - any thoughts?

Test 3 - User sends a private message to another user.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
it('Should be able to send private messages', function(done){
  var client1, client2, client3;
  var message = {to: chatUser1.name, txt:'Private Hello World'};
  var messages = 0;

  var completeTest = function(){
    messages.should.equal(1);
    client1.disconnect();
    client2.disconnect();
    client3.disconnect();
    done();
  };

  var checkPrivateMessage = function(client){
    client.on('private message', function(msg){
      message.txt.should.equal(msg.txt);
      msg.from.should.equal(chatUser3.name);
      messages++;
      if(client === client1){
        /* The first client has received the message
        we give some time to ensure that the others
        will not receive the same message. */
        setTimeout(completeTest, 40);
      };
    });
  };

  client1 = io.connect(socketURL, options);
  checkPrivateMessage(client1);

  client1.on('connect', function(data){
    client1.emit('connection name', chatUser1);
    client2 = io.connect(socketURL, options);
    checkPrivateMessage(client2);

    client2.on('connect', function(data){
      client2.emit('connection name', chatUser2);
      client3 = io.connect(socketURL, options);
      checkPrivateMessage(client3);

      client3.on('connect', function(data){
        client3.emit('connection name', chatUser3);
        client3.emit('private message', message)
      });
    });
  });
});

Once again, running the tests should fail. Lets add code to enable private messages. Ideally we’d use Redis Pub/Sub to simplify message passing, but for the sake of brevity we’ll omit it here. We’ll keep track of clients and their sockets using the clients hash. The hashe’s key will be the client’s name, and the value will be the client’s socket. When the client disconnects we delete that entry from the hash.

Server V0.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var io = require('socket.io').listen(5000);
var clients = {};

io.sockets.on('connection', function (socket) {
  var userName;
  socket.on('connection name',function(user){
    userName = user.name;
    clients[user.name] = socket;
    io.sockets.emit('new user', user.name + " has joined.");
  });

  socket.on('message', function(msg){
    io.sockets.emit('message', msg);
  });

  socket.on('private message', function(msg){
    fromMsg = {from:userName, txt:msg.txt}
    clients[msg.to].emit('private message', fromMsg);
  });

  socket.on('disconnect', function(){
    delete clients[userName];
  });
});

Congratulations, you have a node.js chat server and you’ve tested it!

Testing Socket.IO on Github

Trouble Shooting

  • Have you installed node.io-client, mocha and should?
  • Did you install any of the modules locally, but in another folder? If so try doing a global install.
  • Make sure you restart the server before each test run.
  • Running mocha -R spec will not work within the test folder, this command looks for the test folder, so cd .. if you’re in the test folder.
  • The version of socket.io-client that was in the npm repositories at the time of writing this would not connect to the node server. To get the latest version I ran: npm install https://github.com/LearnBoost/socket.io-client/zipball/master

Updates

Using coffeescript @jamescarr refactored the above code so the server starts automatically during testing: socket.io-test

Comments