【问题标题】:Sinon.Stub in Node with AWS-SDK带有 AWS-SDK 的节点中的 Sinon.Stub
【发布时间】:2014-12-02 07:56:48
【问题描述】:

我正在尝试为使用 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 中模拟对象,我有点新手,所以请原谅我的无知

【问题讨论】:

  • 您找到解决方案了吗?
  • @hyprstack 见过/尝试过 aws-sdk-mock npm 模块吗? (见下面的答案
  • @nelsonic 当时我设法用proxyquire 和 sinon 存根服务并让它工作。我还没有看过 aws-sdk-mock。你用过吗?
  • @hyprstack 是的,我们正在使用 aws-sdk-mock简化了 Sinon.Stub):-)

标签: node.js sinon aws-sdk


【解决方案1】:

这就是我使用 sinonjs 存根 AWS-SDK 的方式

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 的所有方法。希望这会对某人有所帮助

【讨论】:

  • 不知道为什么这个答案没有得到更多的爱,它很简单并且可以完成工作。我已经创建了它的泛化来处理承诺模式(我喜欢使用) - 见下文
【解决方案2】:

我们创建了一个 aws-sdk-mock npm 模块,它模拟了所有 AWS 开发工具包服务和方法。 https://github.com/dwyl/aws-sdk-mock

它真的很容易使用。只需使用服务、方法和存根函数调用 AWS.mock。

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

然后在测试后通过调用恢复方法:

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

【讨论】:

  • 但是我们不能在一个服务中为一个方法模拟多次。
【解决方案3】:

我认为问题在于 AWS 开发工具包类是从 JSON 配置动态构建的。这是 SQS 的一个:Github

所有 API 调用最终都会在 Service 上归结为 makeRequestmakeUnauthenticatedRequest,所以我只是将那些使用 withArgs(...) 的调用存根。例如:

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

这对我的简单用例来说效果很好。

【讨论】:

  • 有什么适合 Lambda 的
  • 花了几个小时终于找到了对我有用的东西。这也是模拟/存根最通用的东西,因为它将涵盖所有 aws 服务。不知道为什么它没有得到足够的认可。伙计,你应该为此获得奖牌。
  • 完美! JSON 定义绝对是问题所在。我找不到存根 SQS 原型的方法,因为这些函数只绑定到实例。
【解决方案4】:

您可以使用以下方法使用 Sinon 存根 AWS 开发工具包方法

  1. 包装 AWS SDK 实例并允许在外部设置 this:

    //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. 因此,使用包装好的 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));
                });
            });
        });
    });
    

【讨论】:

  • 没错,问题是Sinon在尝试stub/spy AWS SDK时遇到了麻烦,所以一种解决方案是stub/spy wrapper方法。这对我们来说并没有那么难看,因为我们已经有了一个现有的方法,因此我们不必继续使用密钥初始化 SDK 等等
【解决方案5】:

您无需引入任何额外的库就可以做到这一点:

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

【讨论】:

  • Kinesis 发布的单行示例:sinon.stub(AWS, 'Kinesis').returns({ putRecord: sinon.stub().callsArgWith(1, null, true) })
【解决方案6】:

我无法确切告诉您为什么 Sinon 无法对 aws sdk 进行存根(也许一些 JS 专家可以更好地解释这一点),但它与 proxyquire 一起工作得很好。

代理 nodejs 的要求是为了在测试期间轻松覆盖依赖项,同时保持完全不引人注目。

【讨论】:

    【解决方案7】:

    我喜欢使用承诺,在上面@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()
    })
    

    【讨论】:

      【解决方案8】:

      有了AWS SDK v3,它变得容易多了。它甚至可以直接使用 Promise,而无需创建嵌入式存根对象。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-12-18
        • 2018-12-26
        • 1970-01-01
        • 2017-08-08
        • 1970-01-01
        • 1970-01-01
        • 2020-08-11
        • 1970-01-01
        相关资源
        最近更新 更多