node.jssocket.iomocha.jsbddexpect.js

Socket.IO server not receiving message from client


I'm playing around with Node, Socket.IO and BDD by creating a chat application. During one of the tests, I get a timeout error stating:

Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.

The affected test is

it('#must be able to receive a message', function(done)
{
    chatterServer.on('chatterMessage', function(data)
    {
        console.log('Incoming message!');
        expect(data).to.have.property('message');
        expect(data.message).to.be('Hello, world!');
        done();
    });

    console.log('Sending message!');
    chatterClient.send('chatterMessage', { message: 'Hello, world!' });
    console.log('Sent!');
});

I found that the cause of this issue is that the chatterMessage event is not being caught by the server. Whilst I did specify it.

The console's output is:

Sending message!
Sent!
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.

I'm probably doing something wrong. I'm not too familiar with Node and Socket.IO, so I'm sorry if this question is very obvious.

I looked around Google with the search terms 'socket.io server not receiving from client', but from what I found, nothing helped me to solve my issue so far.

I did however try the solution in this question, but that didn't fix it for me.

I'm using Mocha and expect.js

The complete test is:

var util = require('util');
var Chatter = require('../src/index');
var ChatterServer = Chatter.Server;
var ChatterClient = Chatter.Client;
var express = require('express');
var expect = require('expect.js');
var socketIO = require('socket.io');
var socketIOClient = require('socket.io-client');

var host = 'http://localhost';
var port = 8080;

describe('Chatter', function()
{
    'use strict';

    var chatterServer;
    var chatterClient;
    var server;

    before(function()
    {
        var app = express();

        server = app.listen(port);
    });

    beforeEach(function()
    {
        chatterServer = new ChatterServer(socketIO(server));
        chatterClient = new ChatterClient(socketIOClient, util.format('%s:%s', host, port.toString()));
    });

    ...

    it('#must be able to receive a message', function(done)
    {
        chatterServer.on('chatterMessage', function(data)
        {
            console.log('Incoming message!');
            expect(data).to.have.property('message');
            expect(data.message).to.be('Hello, world!');
            done();
        });

        console.log('Sending message!');
        chatterClient.send('chatterMessage', { message: 'Hello, world!' });
        console.log('Sent!');
    });
});

My Client (ChatterClient) is:

(function()
{
    'use strict';

    function Client(socketIO, url)
    {
        this.socketIO = socketIO(url);
    }

    Client.prototype.send = function(event, data)
    {
        this.socketIO.emit(event, data);
    };

    Client.prototype.on = function(event, callback)
    {
        this.socketIO.on(event, callback);
    };

    if (module !== undefined && module.hasOwnProperty('exports')) {
        module.exports = Client;
    } else {
        window.Chatter = {
            Client: Client,
        };
    }
}());

The Server (ChatterServer) is:

(function()
{
    'use strict';

    function Server(socketIO)
    {
        this.socketIO = socketIO;
        this.connectedUsers = {};

        this.on('connection', (function(user)
        {
            var userID = user.client.id;

            this.connectedUsers[userID] = user;

            user.emit('chatterConnectionAcknowledged', { id: userID });
        }).bind(this));
    }

    Server.prototype.on = function(event, handler)
    {
        this.socketIO.on(event, handler);
    };

    module.exports = Server;

}());

Solution

  • You need to change your code in two sides.

    First side, you will need to listen incoming socket connections on the socketIO object. (see the emphasized code below)

        //.. some code
    
        function Server(socketIO)
        {
            this.socketIO = socketIO;
            this.connectedUsers = {};
    
            this.socketIO.on('connection', (function(user)
            {
                var userID = user.client.id;
    
                this.connectedUsers[userID] = user;
    
                user.emit('chatterConnectionAcknowledged', { id: userID });
            }).bind(this));
        }
    
        //.. some code
    

    Second side, when you are adding new events to listen on the server, you need to bind those events to the sockets since they are ones that are going to listen when events are emitted from the socket clients.

    Server.prototype.on = function (event, handler) {
      Object.keys(this.connectedUsers).map(function (key) {
        this.connectedUsers[key].on(event, handler);
      }.bind(this));
    };