Sinon.Stub в узле с AWS-SDK

Я пытаюсь написать некоторое тестовое покрытие для приложения, которое использует модуль aws-sdk NPM, который помещает вещи в очередь SQS, но я не уверен, как правильно имитировать вещи.

Вот мой тест на данный момент:

var request = require('superagent'),
    expect = require('chai').expect,
    assert = require('chai').assert,
    sinon = require('sinon'),
    AWS = require('aws-sdk'),
    app = require("../../../../app");

describe("Activities", function () {

    describe("POST /activities", function () {

        beforeEach(function(done) {
            sinon.stub(AWS.SQS.prototype, 'sendMessage');

            done();
        });

        afterEach(function(done) {
            AWS.SQS.prototype.sendMessage.restore();

            done();
        });

        it("should call SQS successfully", function (done) {
            var body = {
                "custom_activity_node_id" : "1562",
                "campaign_id" : "318"
            };

            reqest
            .post('/v1/user/123/custom_activity')
            .send(body)
            .set('Content-Type', 'application/json')
            .end(function(err, res) {
                expect(res.status).to.equal(200)

                assert(AWS.SQS.sendMessage.calledOnce);
                assert(AWS.SQS.sendMessage.calledWith(body));
            });
        });

    });

});

Ошибка, которую я вижу:

  1) Activities POST /activities "before each" hook:
     TypeError: Attempted to wrap undefined property sendMessage as function

  2) Activities POST /activities "after each" hook:
     TypeError: Cannot call method 'restore' of undefined

Я немного новичок, когда дело доходит до sinon.stub или насмешек над объектами в JavaScript, поэтому, пожалуйста, извините мое невежество.


person dennismonsewicz    schedule 07.10.2014    source источник
comment
Вам уже удалось найти решение этой проблемы?   -  person hyprstack    schedule 21.05.2015
comment
@hyprstack видели/пробовали aws-sdk-mock модуль npm? (см. ответ ниже)   -  person nelsonic    schedule 12.02.2016
comment
@nelsonic в то время мне удалось заглушить сервис с помощью proxyquire и sinon, и он заработал. Я еще не видел aws-sdk-mock. Вы использовали его?   -  person hyprstack    schedule 12.02.2016
comment
@hyprstack да, мы используем aws-sdk-mock (что упрощает Sinon.Stub) :-)   -  person nelsonic    schedule 12.02.2016


Ответы (8)


Вот как я заглушаю AWS-SDK с помощью sinonjs

import AWS from 'aws-sdk'
import sinon from 'sinon'

let sinonSandbox

const beforeEach = (done) => {
   sinonSandbox = sinon.sandbox.create()
   done()
}

const afterEach = done => {
   sinonSandbox.restore()
   done()
} 
lab.test('test name', (done) => {
    sinonSandbox.stub(AWS, 'SQS')
      .returns({
        getQueueUrl: () => {
          return {
            QueueUrl: 'https://www.sample.com'
          }
        }
    })
    done()
})

В основном я контролирую все методы из основного SQS. Надеюсь, это поможет кому-то

person kdlcruz    schedule 20.11.2017
comment
Не уверен, почему этот ответ не получил больше любви, он прост и выполняет свою работу. Я создал его обобщение для обработки шаблона обещания (который мне нравится использовать) - см. ниже - person Jason; 13.03.2018

Мы создали aws-sdk-mock модуль npm, который имитирует все Службы и методы SDK. https://github.com/dwyl/aws-sdk-mock

Его действительно легко использовать. Просто вызовите AWS.mock с сервисом, методом и функцией-заглушкой.

AWS.mock('SQS', 'sendMessage', function(params, callback) {
    callback(null, 'success');
});

Затем восстановите методы после ваших тестов, вызвав:

AWS.restore('SQS', 'sendMessage');
person Nikhila Ravi    schedule 10.02.2016
comment
Но мы не можем использовать несколько моков для метода в одном сервисе. - person Angle Tom; 14.06.2016

Я думаю, проблема в том, что классы AWS SDK создаются динамически из конфигурации JSON. Вот вариант для SQS: Гитхаб.

Все вызовы API в конечном итоге сводятся к makeRequest или makeUnauthenticatedRequest в Service., поэтому я просто заглушил тех, кто использует withArgs(...). Например:

var stub = sinon.stub(AWS.Service.prototype, 'makeRequest');
stub.withArgs('assumeRole', sinon.match.any, sinon.match.any)
    .yields(null, fakeCredentials);

который отлично работал для моего простого варианта использования.

person David    schedule 07.11.2016
comment
есть что-нибудь для лямбды - person kailash yogeshwar; 07.07.2018
comment
Наконец, потратив несколько часов, что-то, что сработало для меня. Кроме того, это самая общая вещь для насмешек/заглушек, поскольку она будет охватывать все сервисы aws. Не знаю, почему ее недостаточно признали. Чувак, ты заслуживаешь за это медаль. - person lone_worrior; 24.06.2019
comment
Идеальный! Определение JSON определенно является проблемой. Я не смог найти способ заглушить прототип SQS, поскольку функции привязаны только к экземплярам. - person peterchaula; 30.06.2020

Вы можете заглушить методы AWS SDK с помощью Sinon следующим образом:

  1. Оберните экземпляр AWS SDK и позвольте этому быть установленным извне:

    //Within say, SqsService.js
    var Aws = require('aws-sdk');
    
    exports.sqsClient = new Aws.SQS({
        region: <AWS_REGION>,
        apiVersion: <API_VERSION>,
        accessKeyId: <AWS_ACCESS_KEY_ID>,
        secretAccessKey: <AWS_SECRET_KEY>
    });
    
  2. При использовании sqsClient убедитесь, что вместо этого используется обернутый экземпляр.

    var SqsService = require('./SqsService');
    
    function (message, callback) {
        //Do stuff..
        //Then send stuff..
        SqsService.sqsClient.sendMessage(message, callback);
    });
    
  3. Итак, изменив ваш тестовый пример сверху, используя завернутый SDK AWS:

    var request = require('superagent'),
        expect = require('chai').expect,
        assert = require('chai').assert,
        sinon = require('sinon'),
        SqsService = require('./SqsService'), //Import wrapper
        app = require("../../../../app");
    
    describe("Activities", function () {
    
        describe("POST /activities", function () {
    
            var sendMessageStub;
    
            beforeEach(function(done) {
                //Stub like so here
                sendMessageStub = sinon.stub(SqsService.sqsClient, 'sendMessage').callsArgWith(1, null, { MessageId: 'Your desired MessageId' });
    
                done();
            });
    
            afterEach(function(done) {
                sendMessageStub.restore();
    
                done();
            });
    
            it("should call SQS successfully", function (done) {
                var body = {
                    "custom_activity_node_id" : "1562",
                    "campaign_id" : "318"
                };
    
                reqest
                    .post('/v1/user/123/custom_activity')
                    .send(body)
                    .set('Content-Type', 'application/json')
                    .end(function(err, res) {
                        expect(res.status).to.equal(200)
    
                        assert(sendMessageStub.calledOnce);
                        assert(sendMessageStub.calledWith(body));
                });
            });
        });
    });
    
person gyamana    schedule 26.02.2015
comment
Правильно, проблема в том, что у Sinon возникают проблемы с попыткой заглушить/подсмотреть AWS SDK, поэтому одно из решений — заглушить/подсмотреть метод-оболочку. Для нас это было не так уродливо, так как у нас уже был существующий метод, поэтому нам не нужно было продолжать инициализировать SDK с помощью ключей и тому подобного. - person gyamana; 14.06.2016

Вы можете сделать это без каких-либо дополнительных библиотек, используя что-то вроде этого:

const mocha = require('mocha'),
    chai = require('chai'),
    expect = chai.expect,    // Using Expect style
    sinon = require('sinon'),
    AWS = require('aws-sdk');

describe('app', function () {
    var aws, sqs, app,
        sendMessageError = null,
        sendMessageData = { MessageId: "1" };
    before(() => {
        // Create a stub for the SQS lib
        sqs = sinon.stub({ sendMessage: Function() });
        // Make sure that when someone calls AWS.SQS they get our stub
        aws = sinon.stub(AWS, 'SQS');
        aws.returns(sqs);
        // Now include your app since it will `require` our stubbed version of AWS
        app = require('./app');
    });
    after(() => {
        aws.restore(); // Be kind to future tests
    });
    beforeEach(() => {
        // Reset callback behavior after each test
        sqs.sendMessage.reset();
        // Call the callback supplied to sendMessage in the 1st position with the arguments supplied
        sqs.sendMessage.callsArgWith(1, sendMessageError, sendMessageData);
    });
    it('sends messages', () => {
        // Pretend you're using Promises in your app, but callbacks are just as easy
        return app.sendMessage().then(() => {
            const args = sqs.sendMessage.getCall(0).args[0];
            expect(args.QueueUrl).to.be.eq('http://127.0.0.1/your/queue/url');
        });
    });
});
person Joel B    schedule 18.07.2017
comment
Однострочный пример для Kinesis Publishing: sinon.stub(AWS, 'Kinesis').returns({ putRecord: sinon.stub().callsArgWith(1, null, true) }) - person noetix; 17.11.2017

Я не могу сказать вам точно, почему Sinon не может заглушить aws sdk (может быть, какой-нибудь эксперт JS может объяснить это лучше), но это работает с proxyquire очень хорошо.

Прокси-серверы необходимы для того, чтобы упростить переопределение зависимостей во время тестирования, оставаясь при этом совершенно ненавязчивым.

person Philipp Garbe    schedule 20.02.2015

Мне нравится использовать обещания, основываясь на ответе @kdlcruz выше, я делаю что-то вроде этого:

import AWS from 'aws-sdk'
import sinon from 'sinon'

let sinonSandbox

const beforeEach = (done) => {
   sinonSandbox = sinon.sandbox.create()
   done()
}

const afterEach = done => {
   sinonSandbox.restore()
   done()
} 

function mockAWSCall(service, method, expectedArgs, response) {
    var stubDef = {};
    stubDef[method] = function(args) {
        if(expectedArgs) {
            expect(args).to.deep.equal(expectedArgs);
        }
        return {
            promise: () => {
                return new Promise(function (resolve, reject) {
                    if(response.startsWith("ERROR:")) {
                        reject(response);
                    } else {
                        resolve(response);
                    }
                });
            }
        };
    };

    sinonSandbox.stub(AWS, service).returns(stubDef);
}

lab.test('test name', (done) => {
    mockAWSCall('SQS', 'sendMessage', {
        MessageBody: 'foo', QueueUrl: 'http://xxx'
    }, 'ok');
    // Do something that triggers the call...
    done()
})
person Jason    schedule 13.03.2018

Благодаря AWS SDK v3 это стало намного легче. Он даже работает с промисами напрямую, без необходимости создавать встроенные объекты-заглушки.

  sinon.stub(SQS.prototype, 'sendMessage').resolves({
    SequenceNumber: '0',
  });

  const sqs = new SQS({});
  const result = await sqs.sendMessage({
    MessageBody: '',
    QueueUrl: '',
  });

  expect(SQS.prototype.sendMessage).to.be.calledOnce;
  expect(result.SequenceNumber).to.be('0');
person Stephen Horvath    schedule 17.12.2020