Search code examples
node.jsmocha.jsaws-sdkchaisinon

nodejs: Tests pass individually, fail when run together (mocha, sinon, aws-sdk)


I have tests that:

  1. PASS when run together in VS Code
  2. FAIL when run together on the CLI
  3. PASS when run individually (e.g. mocha bogus.test.js -g "second")

Here's the code as bare bones as I can make it (and it's pretty darned bare bones):

const AWS = require("aws-sdk")
const SQS = new AWS.SQS()

exports.handler = () => {
  return SQS.deleteMessage({foo: "bar"}).promise()
}

and the test:

const sinon = require("sinon")
const expect = require("chai").expect
const AWS = require("aws-sdk")

describe("Bogus Test", () => {
  let sandbox, deleteMessageStub

  beforeEach(() => {
    sandbox = sinon.createSandbox()

    deleteMessageStub = sandbox.fake.returns({ promise: () => Promise.resolve({}) })
    sandbox.stub(AWS, 'SQS').returns({
      deleteMessage: deleteMessageStub
    })  
  })

  afterEach(() => { sandbox.restore() })

  it("is the first test", () => {
    const bogus = require("../bogus")
    return bogus.handler().then(() => {
      expect(deleteMessageStub.callCount).to.equal(1, 'Should have called deleteMessage once')
    })
  })

  it("is the second test", () => {
    const bogus = require("../bogus")
    return bogus.handler().then(() => {
      expect(deleteMessageStub.callCount).to.equal(1, 'Should have called deleteMessage once')
    })
  })
})

Results:

Bogus Test
✓ is the first test
1) is the second test


1 passing (14ms)
1 failing

1) Bogus Test
is the second test:

Should have called deleteMessage once
+ expected - actual

-0
+1

at ... bogus.test.js:29:46

I would be DELIGHTED to find out that I'm doing something stupid...


Solution

  • require a module multiple times, the code of the module scope will only be executed once, because the module is cached in the require.cache object.

    Which means the ./bogus module is only loaded once and is obtained from the require.cache object during the second require. The const SQS = new AWS.SQS(); statement will be executed only once.

    You cleared the call information of the stub through sinon.restore(), so the stub.callCount of the second test case is 0.

    Solution: Clear the module cache of ./bogus within beforeEach() hook

    E.g.

    bogus.js:

    const AWS = require('aws-sdk');
    const SQS = new AWS.SQS();
    
    exports.handler = () => {
      return SQS.deleteMessage({ foo: 'bar' }).promise();
    };
    
    

    bogus.test.js:

    const sinon = require('sinon');
    const expect = require('chai').expect;
    const AWS = require('aws-sdk');
    
    describe('Bogus Test', () => {
      let sandbox, deleteMessageStub;
    
      beforeEach(() => {
        sandbox = sinon.createSandbox();
    
        deleteMessageStub = sandbox.fake.returns({ promise: () => Promise.resolve({}) });
        sandbox.stub(AWS, 'SQS').returns({
          deleteMessage: deleteMessageStub,
        });
        delete require.cache[require.resolve('./bogus')];
      });
    
      afterEach(() => {
        sandbox.restore();
      });
    
      it('is the first test', () => {
        const bogus = require('./bogus');
        return bogus.handler().then(() => {
          expect(deleteMessageStub.callCount).to.equal(1, 'Should have called deleteMessage once');
        });
      });
    
      it('is the second test', () => {
        const bogus = require('./bogus');
        return bogus.handler().then(() => {
          expect(deleteMessageStub.callCount).to.equal(1, 'Should have called deleteMessage once');
        });
      });
    });
    

    unit test result:

      Bogus Test
        ✓ is the first test
        ✓ is the second test
    
    
      2 passing (9ms)