Testing the functionality of an event that is not linked to an event emitter

Below is the code snippet I'm working with:

  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);
  });

Within this code, there is an anonymous function that triggers when a message event is emitted by the mqtt client. I have no control over this function being bound to the event emitter, and I cannot rely on a timer or wait for the event to occur because I have simulated the client's connectivity using sinon for mocking purposes. With no actual connection, how can I manually trigger the message event and verify if the messageReceivedCallBack's onMessageReceived method was executed? I am new to unit testing in Javascript and currently utilizing mocha and sinon for my testing needs.

I appreciate any advice on this matter.

Answer №1

Just a heads up: I want to clarify that my knowledge of TypeScript and the inner workings of your application is limited, so the solutions provided below may not be plug-and-play. However, they can serve as inspiration for implementing similar tests.

I've abstracted the client code that needs testing:

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

// Instead of a class, I opted for a simple function to mimic your current setup.
module.exports = {
  setup() {
    let client = mqtt.connect();
    client.on('message', (topic, message) => {
      // This section is what we aim to test.
      messageReceivedCallBack.onMessageReceived(message);
    });
  }
}

In your existing code, it's unclear where messageReceivedCallBack originates from. To make it accessible by Sinon, ensure it's within an importable module (though this relies on imports being cached like with require(), which TypeScript may or may not do).

Here's a basic mock example used:

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

Now onto the actual test, which involves several steps:

  • Create an EventEmitter subclass to substitute the original MqttClient
  • Stub various functions and callbacks
  • Configure the testing environment

The following snippet outlines these actions:

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

// The aforementioned modules.
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 mock client for testing purposes.
const EventEmitter = require('events').EventEmitter;
class Client extends EventEmitter {}

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

  beforeEach(() => {
    mockClient = new Client();
    // Stub `mqtt.connect()` to return a fake client instance facilitating event emission.
    sinon.stub(mqtt, 'connect').returns(mockClient);

    // Invoke the setup function of our MQTT class after stubbing `mqtt.connect()`.
    mqttModule.setup();
  });

  afterEach(() => {
    // Revert to the original function.
    mqtt.connect.restore();
  });

  it('should invoke messageReceivedCallBack.onMessageReceived', () => {
    // Define the message to pass during testing.
    let message = 'this is a test message';

    // Stub `messageReceivedCallBack.onMessageReceived()`.
    sinon.stub(messageReceivedCallBack, 'onMessageReceived');

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

    // Confirm that the stub was invoked with the correct argument.
    expect(messageReceivedCallBack.onMessageReceived).to.be.calledWith(message);

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

});

Answer №2

class CustomMqttClientMock extends MyEventEmitter {
  sub = fake.sinon()
  pub = fake.sinon()
  unsub = fake.sinon()
}

sandbox.provide(sinon, 'connect').and.returnValue(new CustomMqttClientMock() as undefined as MqttClient)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When Using TypeScript with Serverless, 'this' Becomes Undefined When Private Methods are Called from Public Methods

Currently, I am working on constructing an AWS Serverless function using TypeScript. My focus is on creating an abstract class with a single public method that invokes some private methods. Below is the simplified version of my TypeScript class: export ...

Having trouble establishing a connection between Atlas and Node.js

Here is the content of my server.js file: const express = require('express'); const cors = require('cors'); const mongoose = require('mongoose'); require('dotenv').config(); const app = express(); const port = pro ...

"pre and post" historical context

Is there a way to create a stunning "Before and After" effect using full-sized background images? It would be amazing if I could achieve this! I've been experimenting with different examples but can't seem to get the second 'reveal' di ...

Enhancing the JQuery Dialog box by assigning an ID to the standard close button ("X") icon

Is there a method to instruct JQuery to assign an id to the default close "X" link widget (located at the top right of the dialog window in the header bar) when initializing the dialog? I am aware that you can assign an ID to the OK/Cancel buttons by spec ...

Script tag for Next.js reloading functionality

I have been facing a challenge while trying to integrate third-party commenting scripts like (disqus, remark42, hyvor) into my next.js application. The issue I encountered is that the script only loads on the initial page load. Subsequently, I need to refr ...

Activate Gulp watch to run task just one time

I have integrated gulp-livereload to automatically refresh pages after modifying .js files. Below is the code snippet: const gulp = require('gulp'); const livereload = require('gulp-livereload'); gulp.task('jsLiveReload', ...

delivering several requests using Promises

I have experimented with encapsulating two query responses within an object and then using that object as the return value in a function app.get('/gerais',(req,res) => { const client = new Client(); const data = new Object(); clie ...

What is the best way to display a removed item from the Redux state?

Display nothing when the delete button is clicked. The issue seems to be with arr.find, as it only renders the first item regardless of which button is pressed, while arr.filter renders an empty list. reducer: export default function reducer(state = initi ...

The function json.stringify fails to invoke the toJson method on every object nested within the main object

When using IE 11, I encountered an issue where calling Stringify on my objects did not recursively call toJson on all objects in the tree. I have implemented a custom toJson function. Object.prototype.toJSON = function() { if (!(this.constructor.name == ...

Using res.sendfile in a Node Express server and sending additional data along with the file

Can a Node.JS application redirect to an HTML file using the res.sendFile method from express and include JSON data in the process? ...

Changing the state variable upon clicking to display varying components

Being new to React, I am exploring how to load different components based on state variables. I am looking for a way for my handler to dynamically determine which state variable I am updating without explicitly passing the state name as a string. List of ...

Eliminate values from an array by utilizing either a for loop or a custom filtering function

I need help with removing specific URLs from an array every time they appear. Here is the list of URLs I want to filter out: "https://basueUrl.com/Claim" "https://basueUrl.com/ExplanationOfBenefit" This is my current array: Array= [ "https://b ...

Is it necessary to use the "new" keyword when utilizing JS closure to create objects?

My response to a question about closures on SO included the following code sample: function Constructor() { var privateProperty = 'private'; var privateMethod = function(){ alert('called from public method'); }; ...

Is there a way to combine multiple incoming WebRTC audio streams into one cohesive stream on a server?

I am currently working on developing an audio-conferencing application for the web that utilizes a WebSocket server to facilitate connections between users and enables streaming of audio. However, I am looking to enhance the system by transforming the serv ...

Name of Document (changing from php to ?)

Greetings! Currently, I am utilizing PHP to include the document name as an example. folder/<?phpecho basename(__FILE__, '.' . pathinfo(__FILE__, PATHINFO_EXTENSION));?>_button.png I have observed that using PHP for this purpose and havin ...

Avoid turning negative numbers into NaN by ensuring that you do not divide by zero

When attempting to convert NaN to false, negative numbers are also affected. -0.2|0 //this operation will always result in zero when the number is negative I wanted to perform a bitwise operation quickly and inline, minimizing the number of steps, as ...

Sending a function parameter when registering for an event

I am currently utilizing the jQuery pubsub for creating custom events: (function($) { var o = $({}); $.subscribe = function() { o.on.apply(o, arguments); }; $.unsubscribe = function() { o.off.apply(o, arguments); }; $.publish = fun ...

Instructions for applying a border style to a dynamically generated table using jQuery

I'm currently working on adding CSS to a table that is generated dynamically when a button is clicked. The function that is triggered on the click event includes the following jQuery code to create the dynamic rows: $("#employeDetail").append('& ...

What exactly entails static site generation?

I have been exploring the concept of static site generation (SSG). The interesting question that arises is how SSG can fetch data at build time. In my latest application, I implemented an event handler to retrieve data based on a user's search keyword ...

Challenges with ColdFusion's floating point calculations

I am encountering an issue with my program where it is not displaying two decimal points properly. For example, when I enter 140.00, it shows as 140.0. Strangely, if I enter 140.15, it displays correctly as 140.15. It seems to consistently drop the zero. B ...