4

Node version: v12.18.3 Sails version (sails): 1.2.3


I am unable to stub a sails helper when performing unit tests. I have a helper that handles all the communication with a database. Moreover, I have an API, which uses this helper. In my tests, I am trying to stub the helper using sinon as such:

The API:

fn: async function (inputs, exits) {
 // Stuff done here
 // I need to stub this helper
 let result = await sails.helpers.arangoQuery.with({
      requestId: REQUEST_ID,
      query: query,
      queryParams: params
    });
  
}

My test:

describe('Get Organization', () => {
    it('Server Error - Simulates a failure in fetching the data from ArangoDB', (done) => {
        sinon.stub(sails.helpers, 'arangoQuery').returns(null, {status: "success"});

        supertest(sails.hooks.http.app)
            .get('/organization')
            //.expect(200)
            .end((error, response) => {
              return done()
            }
      })
})

When I run the test, I get the following error:

  error: Error: cannot GET /organization (500)
      at Response.toError (/opt/designhubz/organization-service/node_modules/superagent/lib/node/response.js:94:15)
      at ResponseBase._setStatusProperties (/opt/designhubz/organization-service/node_modules/superagent/lib/response-base.js:123:16)
      at new Response (/opt/designhubz/organization-service/node_modules/superagent/lib/node/response.js:41:8)
      at Test.Request._emitResponse (/opt/designhubz/organization-service/node_modules/superagent/lib/node/index.js:752:20)
      at /opt/designhubz/organization-service/node_modules/superagent/lib/node/index.js:916:38
      at IncomingMessage.<anonymous> (/opt/designhubz/organization-service/node_modules/superagent/lib/node/parsers/json.js:19:7)
      at IncomingMessage.emit (events.js:327:22)
      at endReadableNT (_stream_readable.js:1220:12)
      at processTicksAndRejections (internal/process/task_queues.js:84:21) {
    status: 500,
    text: '{}',
    method: 'GET',
    path: '/organization'
  }

There are no documentations at all regarding this issue. Can anyone tell me how I can stub a helper?

Nicolas El Khoury
  • 5,867
  • 4
  • 18
  • 28
  • Can you give more context? Test spec and/or code snippet more than 1 liner. From that 1 line, you can check at .returns usage. Based on documentation, it able to handle 1 argument. (https://sinonjs.org/releases/latest/stubs/#stubreturnsobj) – andreyunugro Sep 08 '20 at 03:11
  • Do you create custom helper arangoQuery ? – andreyunugro Sep 08 '20 at 16:41
  • Yes of course I have. As I said, it handles the communication with the database. I did not include it here because its content is irrelevant. However, it is working normally. – Nicolas El Khoury Sep 09 '20 at 05:35

1 Answers1

7

Sails helpers uses machine, this makes stub making trickier.

AFAIK, the alternative to stub sails helpers is by stubbing the real fn function, because machine will call helper's fn function.

Update: change example that use supertest.

For example:

  • I create endpoint GET /hello using HelloController,
  • I use helpers format-welcome-message from helper's example,
  • I create test spec for endpoint GET /hello.
  • I run it using mocha without lifecycle.js but embed the lifecycle inside test spec (reference).

Endpoint GET /hello definition:

// File: HelloController.js
module.exports = {
  hello: async function (req, res) {
    // Dummy usage of helper with predefined input test.
    const output = await sails.helpers.formatWelcomeMessage.with({ name: 'test' });
    // Just send the output.
    res.send(output);
  }
};

And do not forget to add route: 'GET /hello': 'HelloController.hello' at config/routes.js.

Test spec contains 3 cases (normal call, stub error, and stub success).

// File: hello.test.js
const sails = require('sails');
const sinon = require('sinon');
const { expect } = require('chai');
const supertest = require('supertest');

describe('Test', function () {
  let fwm;
  // Copy from example testing lifecycle.
  before(function(done) {
    sails.lift({
      hooks: { grunt: false },
      log: { level: 'warn' },
    }, function(err) {
      if (err) { return done(err); }
      // Require helper format welcome message here!
      fwm = require('../api/helpers/format-welcome-message');
      return done();
    });
  });

  after(function(done) {
    sails.lower(done);
  });

  it('normal case', function (done) {
    // Create spy to make sure that real helper fn get called.
    const spy = sinon.spy(fwm, 'fn');
    supertest(sails.hooks.http.app)
      .get('/hello')
      .expect(200)
      // Expect endpoint output default value.
      .expect('Hello, test!')
      .end(function() {
        // Make sure spy is called.
        expect(spy.calledOnce).to.equal(true);
        // Restore spy.
        spy.restore();
        done();
      });
  });

  it('case stub error', function (done) {
    // Stub the real fn function inside custom helper.
    const stubError = sinon.stub(fwm, 'fn');
    stubError.callsFake(async function (input, exits) {
      // Setup your error here.
      exits.error(new Error('XXX'));
    });

    supertest(sails.hooks.http.app)
      .get('/hello')
      .expect(500)
      .end(function() {
        // Make sure stub get called once.
        expect(stubError.calledOnce).to.equal(true);
        // Restore stub.
        stubError.restore();
        done();
      });
  });

  it('case stub success', function (done) {
    // Define fake result.
    const fakeResult = 'test';
    // Stub the real fn function inside custom helper.
    const stubSuccess = sinon.stub(fwm, 'fn');
    stubSuccess.callsFake(async function (input, exits) {
      // Setup your success result here.
      exits.success(fakeResult);
    });

    supertest(sails.hooks.http.app)
      .get('/hello')
      // Expect endpoint to output fake result.
      .expect(fakeResult)
      .end(function() {
        // Make sure stub get called once.
        expect(stubSuccess.calledOnce).to.equal(true);
        // Restore stub.
        stubSuccess.restore();
        done();
      });
  });
});

When I run it using mocha:

$ npx mocha test/hello.test.js 

  Test
    ✓ normal case
error: Sending 500 ("Server Error") response: 
 Error: XXX
    at Object.<anonymous> ...
    ✓ case stub error
    ✓ case stub success

  3 passing (407ms)

$
andreyunugro
  • 1,096
  • 5
  • 18
  • Thanks! The problem is the API is the one using arangoQuery. Whenever we call the API using supertest arangoQuery doesn't return the mocked value but still tries to access the database. Any thoughts? – Nick Sep 09 '20 at 16:45
  • Will this work if you're calling the API (that is using this helper) through supertest? – Nicolas El Khoury Sep 09 '20 at 22:58
  • Updated. You can check it. :) – andreyunugro Sep 10 '20 at 05:35
  • if you don't mind me asking is there a correct format to add the stubs into the "before" and "after" functionality? – charbel k Sep 10 '20 at 19:36
  • What you mean by correct format? Code style? The above stub example will work too if you move the stub definition at before and stub restoration at after. Declare the stub once at before, define its behaviour on each test case (or define all of its behaviour at once before), and restore once at after. https://sinonjs.org/releases/latest/stubs/#stuboncalln-added-in-v18 – andreyunugro Sep 11 '20 at 03:03