1

In the following example, I've two functions doTask and doSubTask. Both are async, but due to some business need, I need to fire & forget doSubTask twice with different parameters

const wait = ms => new Promise(resolve => setTimeout(resolve, ms))

const doTask = async (taskId) => {
  await wait(taskId)
  doSubTask(taskId * 3)
  doSubTask(taskId * 5)
  console.log('end task :' + taskId)
}

const doSubTask = async (subTaskId) => {
  await wait(subTaskId)
  console.log('end subTask :' + subTaskId)
}

doTask(2000)

How can I write a test case for doTask, and assert that doSubTask has been called twice?


Here's a code sample, you can run it on runkit.com

require("@fatso83/mini-mocha").install()

const sinon = require("sinon@7.5.0")
const referee = require("@sinonjs/referee")
const chai = require('chai')
chai.use(require('sinon-chai'))
const expect = chai.expect

const wait = ms => new Promise(resolve => setTimeout(resolve, ms))

const doTask = async (taskId) => {
  await wait(taskId)
  doSubTask(taskId * 3)
  doSubTask(taskId * 5)
  console.log('end task :' + taskId)
}

const doSubTask = async (subTaskId) => {
  await wait(subTaskId)
  console.log('end subTask :' + subTaskId)
}

const app = {
  doTask,
  doSubTask,
}

describe("stub", function () {
  it("doSubTask should be called twice", async function () {
    const doSubTaskSpy = sinon.spy(app, 'doSubTask')
    
    await doTask(2000)
    
    expect(doSubTaskSpy).to.be.calledTwice()
  })
})

Output from the above code is:

stub
end task :2000
❌ doSubTask should be called twice (Failed with: "expecte…e been called exactly twice, but it was called 0 times")
end subTask :6000
end subTask :10000
Lin Du
  • 88,126
  • 95
  • 281
  • 483
Mahmoud Samy
  • 2,822
  • 7
  • 34
  • 78

1 Answers1

1

We should use rewire package to require the module and stub doSubTask function. Besides, we should use this way to use fake timers and advance the time when you use promise and setTimeout together.

E.g.

index.js:

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const doTask = async (taskId) => {
  await wait(taskId);
  doSubTask(taskId * 3);
  doSubTask(taskId * 5);
  console.log('end task :' + taskId);
};

const doSubTask = async (subTaskId) => {
  await wait(subTaskId);
  console.log('end subTask :' + subTaskId);
};

const app = {
  doTask,
  doSubTask,
};

module.exports = app;

index.test.js:

const rewire = require('rewire');
const sinon = require('sinon');

describe('stub', function () {
  let clock;
  before(() => {
    clock = sinon.useFakeTimers();
  });
  after(() => {
    clock.restore();
  });
  it('doSubTask should be called twice', async function () {
    const doSubTaskStub = sinon.stub();
    const mod = rewire('./');
    mod.__set__('doSubTask', doSubTaskStub);
    const promise = mod.doTask(2000);
    clock.tick(2010);
    await promise;
    sinon.assert.calledTwice(doSubTaskStub);
    sinon.assert.calledWithExactly(doSubTaskStub, 6000);
    sinon.assert.calledWithExactly(doSubTaskStub, 10000);
  });
});

test result:

  stub
end task :2000
    ✓ doSubTask should be called twice (738ms)


  1 passing (743ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |   84.62 |      100 |      75 |   81.82 |                   
 index.js |   84.62 |      100 |      75 |   81.82 | 11-12             
----------|---------|----------|---------|---------|-------------------
Lin Du
  • 88,126
  • 95
  • 281
  • 483