timhc22
timhc22

Reputation: 7451

Sinon.restore not working for stubbing and testing AWS functions

So I'm trying to write a few tests for testing an AWS wrapper library that I have been writing. The tests are running individually without any issues, but won't all run as one 'describe' block.

const AWS_REGION = 'eu-west-2';

const aws = require('aws-sdk');
const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');
const sinonChai = require('sinon-chai');
chai.use(sinonChai);

// These help:
// https://stackoverflow.com/questions/26243647/sinon-stub-in-node-with-aws-sdk
// https://stackoverflow.com/questions/61516053/sinon-stub-for-lambda-using-promises
describe('SQS Utilities Test', () => {

  afterEach(() => {
    sinon.restore();
  });

  it('should add to SQS', async () => {
    sinon.stub(aws.config, 'update');

    const sqs = {
      sendMessage: sinon.stub().returnsThis(),
      promise: sinon.stub()
    };
    sinon.stub(aws, 'SQS').callsFake(() => sqs);

    // these use the above stubbed version of aws
    const AWSUtilities = require('../index').AWSUtilities;
    const awsUtilities = new AWSUtilities(AWS_REGION);
    const response = await awsUtilities.postToSQS('https://example.com', { id: 1}, 'chicken');

    expect(sqs.sendMessage).to.have.been.calledOnce;
  });

  it('should get from SQS', async () => {
    sinon.stub(aws.config, 'update');

    const sqs = {
      receiveMessage: sinon.stub().returnsThis(),
      promise: sinon.stub()
    };
    sinon.stub(aws, 'SQS').callsFake(() => sqs);

    // these use the above stubbed version of aws
    const AWSUtilities = require('../index').AWSUtilities;
    const awsUtilities = new AWSUtilities(AWS_REGION);

    const response = await awsUtilities.getFromSQS('https://example.com');
    expect(sqs.receiveMessage).to.have.been.calledOnce;
  });

...

What I noticed, is that in the second test, the error I am getting is sqs.receiveMessage is not a function, which means that the second test is using the sqs object from the first test (I can further verify this as the error changes if I add receiveMessage to the first test sqs object).

Is this a bug in sinon restore, or have I written something incorrectly? Here is the whole library: https://github.com/unegma/aws-utilities/blob/main/test/SQSTests.spec.js

Upvotes: 1

Views: 1345

Answers (1)

Josnidhin
Josnidhin

Reputation: 12504

This is not an issue with Sinon. This an issue of how you are stubbing AWS SDK. Let's break down what's happening within the code you have shared.

const sqs = {
  sendMessage: sinon.stub().returnsThis(),
  promise: sinon.stub()
};
sinon.stub(aws, 'SQS').callsFake(() => sqs);

// these use the above stubbed version of aws
const AWSUtilities = require('../index').AWSUtilities;

This code does the following

  1. Stub SQS of aws.
  2. Load AWSUtilities.js (based on the source code in github)

AWSUtilities.js does the following as soon as its loaded

const aws = require('aws-sdk');
const sqs = new aws.SQS();

// code removed to demo the concept

The above code creates an internal sqs object, which in this case is made using the stubbed aws module. In node once a module is loaded using require it's cached in memory i.e the above code executes only once.

So when the first it() executes it in turn loads AWSUtilities.js for the first time and is cached. Any subsequent calls gets the cached version. When you call sinon.restore it only restores the SQS function of aws module it doesn't restore the sqs object that was created within AWSUtilities.js.

I hope that explains the reason for the behavior that you are seeing.

There are multiple ways to fix this issue. Dependency injection, using modules like proxyquire, rewire, stubbing aws from a central location before all test cases etc.

The following is an option to fix it in just the test cases shown here.

describe('SQS Utilities Test', () => {
  let AWSUtilities, sqsStub;

  before(() => {
    sinon.stub(aws.config, 'update');

    sqsStub = {
      sendMessage: sinon.stub().returnsThis(),
      receiveMessage: sinon.stub().returnsThis(),
      promise: sinon.stub()
    };
    sinon.stub(aws, 'SQS').callsFake(() => sqs);
    AWSUtilities = require('../index').AWSUtilities;
  });

  after(() => {
    sinon.restore();
  });

  it('should add to SQS', async () => {
    const awsUtilities = new AWSUtilities(AWS_REGION);
    const response = await awsUtilities.postToSQS('https://example.com', { id: 1}, 'chicken');
    expect(sqsStub.sendMessage).to.have.been.calledOnce;
  });

  it('should get from SQS', async () => {
    const awsUtilities = new AWSUtilities(AWS_REGION);
    const response = await awsUtilities.getFromSQS('https://example.com');
    expect(sqsStub.receiveMessage).to.have.been.calledOnce;
  });
});

Upvotes: 2

Related Questions