AnOldSoul
AnOldSoul

Reputation: 4197

Unit testing an event which is not bound by event emitter

I have the below code.

  private client: any;

  this.client = mqtt.connect(url, mqttOptions);

  this.client.on('message', (topic: string, message: string) => {
   console.log('came inside onMessage');
    let ksiotMessage: KSIOTMessage;
    let receivedTopic: KSIOTTopic;
    receivedTopic = getValue(topic);
    ksiotMessage = {
      ksiotTopic: receivedTopic,
      payload: message
    };
    messageReceivedCallBack.onMessageReceived(ksiotMessage);
  });

In here, there is an anonymous function that gets fired when a message event is emitted by mqtt client. I have no control over this, meaning its not I, who has bound this to an event emitter. Also I cannot add a timer and wait for this event to fire as I have mocked the connectivity of the client using sinon. So there is no real connection. So how can I emit this message event manually and see if the messageReceivedCallBack's onMessageReceived was called? I am new to unit testing in Javascript. Currently I am using mocha and sinon for my unit testing requirements.

Please advice.

Upvotes: 0

Views: 1728

Answers (2)

hojin
hojin

Reputation: 1395

export class MqttClientMock extends events.EventEmitter {
  subscribe = sinon.stub()
  publish = sinon.stub()
  unsubscribe = sinon.stub()
}

sandbox.stub(mqtt, 'connect').returns(new MqttClientMock() as undefined as MqttClient)

Upvotes: 0

robertklep
robertklep

Reputation: 203251

Big disclaimer first: I don't know TypeScript, nor the internals of your application, so the following may not work out-of-the-box. However, it may provide you with some ideas on how to implement these sorts of tests.

I abstracted your client code (the code thats needs to be tested):

// mqtt-module.js
const mqtt = require('mqtt');
const messageReceivedCallBack = require('./callbacks');

// This mimics what your code does. Instead of a class I just make it a
// simple function.
module.exports = {
 setup() {
    let client = mqtt.connect();
    client.on('message', (topic, message) => {
      // We want to test this.
      messageReceivedCallBack.onMessageReceived(message);
    });
  }
}

In your code, it's not clear where messageReceivedCallBack is coming from. Because you have to be able to access it from Sinon, it should be in an importable module (however, this does rely on the fact that imports are cached, like with require(), of which I'm not sure if TS does this).

Here's a simple mock that I used:

// callbacks.js
module.exports = {
  onMessageReceived(message) {}
};

Finally, the test itself. It's rather elaborate because it needs to do various things:

  • create a EventEmitter subclass that is used to replace the original MqttClient with
  • stub the various functions and callbacks
  • set up the testing environment

The code:

// test.js
const mqtt = require('mqtt');

// The modules mentioned above.
const mqttModule              = require('./mqtt-module');
const messageReceivedCallBack = require('./callbacks');

// Set up Sinon and Chai
const sinon = require('sinon');
const chai  = require('chai');
let expect  = chai.expect;
chai.use(require('sinon-chai'));

// Create a fake client for testing purposes.
const EventEmitter = require('events').EventEmitter;
class Client extends EventEmitter {}

// The test case.
describe('my test case', () => {
  var mockClient;

  beforeEach(() => {
    mockClient = new Client();
    // mqtt.connect() returns a fake client instance, with
    // just enough logic to allow events to be emitted and
    // received.
    sinon.stub(mqtt, 'connect').returns(mockClient);

    // Call the setup of our MQTT class (this will likely be
    // entirely different in your case, but the idea is that
    // it gets called _after_ `mqtt.connect()` gets stubbed.
    mqttModule.setup();
  });

  afterEach(() => {
    // Restore the original.
    mqtt.connect.restore();
  });

  it('should call messageReceivedCallBack.onMessageReceived', () => {
    // The message that we're going to pass.
    let message = 'this is a test message';

    // We want to stub messageReceivedCallBack.onMessageReceived()
    sinon.stub(messageReceivedCallBack, 'onMessageReceived');

    // Emit a `message` event on our mock client, which will trigger
    // the `client.on('message', ...)` in your MQTT class.
    mockClient.emit('message', 'topic', message);

    // Test if the stub was called with the proper argument.
    expect(messageReceivedCallBack.onMessageReceived).to.be.calledWith(message);

    // Restore to the original function.
    messageReceivedCallBack.onMessageReceived.restore();
  });

});

Upvotes: 4

Related Questions