Sinon.Stub in Node with AWS-SDK

半世苍凉 提交于 2019-12-03 08:58:48

问题


I am trying to write some test coverage for an app that uses the aws-sdk NPM module that pushes things up to a SQS queue, but I am unsure how to mock things correctly.

Here is my test so far:

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

    });

});

The error I am seeing is:

  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

I am a bit of a newb when it comes to sinon.stub or mocking objects in JavaScript, so please excuse my ignorance


回答1:


We have created an aws-sdk-mock npm module which mocks out all the AWS SDK services and methods. https://github.com/dwyl/aws-sdk-mock

It's really easy to use. Just call AWS.mock with the service, method and a stub function.

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

Then restore the methods after your tests by calling:

AWS.restore('SQS', 'sendMessage');



回答2:


This is how I stub the AWS-SDK using 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()
})

Basically I control all the methods from the main SQS. Hope this will help someone




回答3:


You can stub the AWS SDK methods with Sinon with the following

  1. Wrap the AWS SDK instance and allow the this to be set externally:

    //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. When using the sqsClient, ensure to use the wrapped instance instead.

    var SqsService = require('./SqsService');
    
    function (message, callback) {
        //Do stuff..
        //Then send stuff..
        SqsService.sqsClient.sendMessage(message, callback);
    });
    
  3. So modifying your test case from above, using the wrapped AWS SDK:

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



回答4:


You can do it without bringing in any extra libraries using something like this:

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



回答5:


I think the issue is that AWS SDK classes are built dynamically from JSON configuration. Here's the one for SQS: Github.

All API calls eventually make it down to makeRequest or makeUnauthenticatedRequest on Service, so I just stubbed those using withArgs(...). For example:

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

which worked fine for my simple use case.




回答6:


I can't tell you exactly why it is not possible with Sinon to stub the aws sdk (maybe some JS expert can explain that better) but it works with proxyquire very well.

Proxies nodejs's require in order to make overriding dependencies during testing easy while staying totally unobstrusive.




回答7:


I like to use promises, building upon @kdlcruz's answer above, I do something like this:

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


来源:https://stackoverflow.com/questions/26243647/sinon-stub-in-node-with-aws-sdk

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!