Stubbing MQTT.js library in Ember.js tests with Sinon.JS
Tags: ember, javascript, testing, webdev
Published: 2021-11-01 13:42:49

<p>I took the opportunity to upgrade the <a href="https://github.com/domoticalabs/ember-mqttjs" title="ember-mqttjs">ember-mqttjs</a> support to <a href="https://emberjs.com/releases/" title="Ember.js releases">Ember v4</a> for refactoring the addons tests. During last <a href="https://emberfest.eu/" title="EmberFest 2021">EmberFest</a> (which happened in Rome on Sept. 30 and Oct. 1, 2021) I listened to a lot of brilliant talks. One of them caught my attention because it dealt with the testing topic and in particular of mocks and stubs.</p>
<p><a title="Gonçalo Morais twitter account's" href="https://twitter.com/gnclmorais">Gonçalo Morais</a> dove into this argument with some stunning hints during his talk <a href="https://emberfest.eu/schedule/#goncalo-morais">"Mock &amp; Roll"</a> that opened my minds and let me wondering on how to improve my mqtt ember addon tests.</p>
<p>After some experiments with the mock-socket library I realized that this tool was not anymore mantained and combining some searchs into the <a href="https://sinonjs.org/" title="Sinon.JS javascript library">Sinon.JS</a> documentation I found a way to <a href="https://sinonjs.org/how-to/stub-dependency/" title="Sinon.JS stub dependency how-to">stub the MQTT.js dependency</a> faking the connect method.</p>
<p>This method returns a client and triggers some events that are useful to understand the mqtt connection status. Joining the replace and fake methods from Sinon.JS I found a way to simulate the MQTT.js connect method behavior returning a client with the necessary methods and triggering the needed events.</p>
<p>As you can see next on the code sample attached to this post I need to add some tricks to the joke, because for example the <em>connect</em> event needs to be trigger after a delay while the fake client declares an event handler that must to be there when the event is fired. Or for example the subscribe and publish methods need to call a callback with the correct parameters.</p>
<p>Once I discovered this tricks the tests runs correctly and I can assume that my code is working fine relying on the MQTT.js client to be properly tested.</p>

##Code sample##
This is the example for the connect test but you can find the whole code into the addon repository:

```javascript
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import sinon from 'sinon';
import mqttjs from 'mqtt/dist/mqtt';
import Service from '@ember/service';
import Evented from '@ember/object/evented';
import { later } from '@ember/runloop';
 
class MqttServiceStub extends Service.extend(Evented) {}
 
module('Unit | Service | mqtt', function (hooks) {
  let mqttHost = 'ws://localhost:8883';
  let mqttTopic = 'presence';
  let mqttMessage = 'Hello';
 
  let mqttServiceStub;
 
  setupTest(hooks);
 
  hooks.afterEach(() => {
    sinon.restore();
  });
 
  // ...
 
  //Testing mqtt connect
  test('mqtt connect success', async function (assert) {
    let service = this.owner.lookup('service:mqtt');
    let done = assert.async();
    assert.expect(3);
    mqttServiceStub = new MqttServiceStub();
    sinon.replace(
      mqttjs,
      'connect',
      sinon.fake(() => {
        later(() => {
          mqttServiceStub.trigger('connect');
        }, 100);
        return {
          on: (sEvent) => {
            mqttServiceStub.on(sEvent, () => {
              if (sEvent === 'connect') {
                return service.onConnect();
              }
            });
          },
        };
      })
    );
    try {
      service.on('mqtt-connected', () => {
        assert.ok(true);
      });
      await service.connect(mqttHost);
      assert.ok(service);
      assert.ok(service.isConnected);
    } catch {
      assert.ok(false);
      assert.ok(false);
      assert.ok(false);
    } finally {
      done();
    }
  });
```
<p>If you notice some errors or you need more informations about the code I'm happy to hear from you. Reach me through my <a href="https://blog.davideferrero.com/contacts/" title="Contact me">contact page</a>.</p>