Sinon.restore não funciona para criar stub e testar funções AWS

Nov 26 2020

Portanto, estou tentando escrever alguns testes para testar uma biblioteca de wrapper da AWS que venho escrevendo. Os testes estão sendo executados individualmente sem nenhum problema, mas nem todos serão executados como um bloco de 'descrição'.

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;
  });

...

O que percebi, é que no segundo teste, o erro que estou recebendo é sqs.receiveMessage is not a function, o que significa que o segundo teste está usando o sqsobjeto do primeiro teste (posso verificar isso posteriormente, pois o erro muda se eu adicionar receiveMessageao primeiro sqsobjeto de teste )

Este é um bug na restauração do sinon ou escrevi algo incorretamente? Aqui está toda a biblioteca:https://github.com/unegma/aws-utilities/blob/main/test/SQSTests.spec.js

Respostas

1 Josnidhin Dec 16 2020 at 10:59

Este não é um problema com a Sinon. Este é um problema de como você está criando o SDK do AWS. Vamos analisar o que está acontecendo no código que você compartilhou.

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;

Este código faz o seguinte

  1. Esboço SQSde aws.
  2. Carregar AWSUtilities.js(com base no código-fonte no github)

AWSUtilities.js faz o seguinte assim que é carregado

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

// code removed to demo the concept

O código acima cria um sqsobjeto interno , que neste caso é feito usando o módulo aws stubbed. No nó, uma vez que um módulo é carregado, requireele é armazenado em cache na memória, ou seja, o código acima é executado apenas uma vez.

Portanto, quando o primeiro é it()executado, ele carrega AWSUtilities.jspela primeira vez e é armazenado em cache. Qualquer chamada subsequente obtém a versão em cache. Quando você o chama sinon.restoreapenas restaura a SQSfunção do awsmódulo, ele não restaura o sqsobjeto que foi criado dentro AWSUtilities.js.

Espero que isso explique o motivo do comportamento que você está vendo.

Existem várias maneiras de corrigir esse problema. Injeção de dependência, usando módulos como proxyquire, rewire, stubbing aws de um local central antes de todos os casos de teste, etc.

A seguir está uma opção para corrigi-lo apenas nos casos de teste mostrados aqui.

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;
  });
});