Node.js 带有AWS-SDK的Sinon.存根节点

Node.js 带有AWS-SDK的Sinon.存根节点,node.js,sinon,aws-sdk,Node.js,Sinon,Aws Sdk,我正在尝试为一个应用程序编写一些测试覆盖范围,该应用程序使用aws sdkNPM模块将内容推送到SQS队列,但我不确定如何正确模拟内容 以下是我目前的测试: var request = require('superagent'), expect = require('chai').expect, assert = require('chai').assert, sinon = require('sinon'), AWS = require('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中的模拟对象时,我有点新手,所以请原谅我的无知

我不能确切地告诉你为什么sinon不可能对aws sdk进行stub(也许一些JS专家可以更好地解释),但它可以很好地使用

为了使测试期间覆盖依赖项变得容易,同时保持完全不受干扰,代理节点需要


您可以使用Sinon将AWS SDK方法存根为以下内容

  • 包装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>
    });
    
  • 因此,使用包装好的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));
                });
            });
        });
    });
    
  • 我们已经创建了一个npm模块,它模拟了所有AWS SDK服务和方法

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

    AWS.mock('SQS', 'sendMessage', function(params, callback) {
        callback(null, 'success');
    });
    
    然后通过调用以下命令在测试后恢复方法:

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

    我认为问题在于AWS SDK类是根据JSON配置动态构建的。这是一个用于SQS的示例:

    所有的API调用最终都归结为
    makeRequest
    makeUnauthenticatedRequest
    打开,所以我只是用args(…)删除了那些使用
    的调用。例如:

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

    对于我的简单用例来说,这很好。

    您可以使用以下内容来完成此操作,而无需引入任何额外的库:

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

    这就是我使用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控制所有方法。希望这能帮助某人

    我喜欢使用承诺,在上面@kdlcruz的答案的基础上,我做了如下事情:

    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');
            });
        });
    });
    
    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()
    })
    
    有了它,事情变得容易多了。它甚至可以直接使用Promission,而无需创建嵌入的存根对象

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

    您找到解决方案了吗?@hyprstack已经看过/尝试过aws sdk模拟npm模块了吗?(请参阅下面的答案)@nelsonic当时我设法用和sinon将服务存根并使其正常工作。我还没看过aws sdk模拟版。您使用过它吗?@hyprstack是的,我们使用的是aws sdk mock(它简化了
    Sinon.Stub
    ):-)谢谢,这很有效!但是我们不能在一个服务中多次模拟一个方法。没错,问题是Sinon在尝试存根/刺探AWS SDK时遇到困难,因此一个解决方案是存根/刺探包装器方法。这对我们来说并不是那么难看,因为我们已经有了一个现有的方法,这样我们就不必一直用键初始化SDK,也不必像Kinesis发布的一个线性示例那样:
    sinon.stub(AWS,“Kinesis”).returns({putRecord:sinon.stub().callsArgWith(1,null,true)}
    不知道为什么这个答案没有得到更多的爱,它很简单,而且可以完成任务。我已经创建了一个处理承诺模式的泛化(我喜欢使用这个模式)——请看下文,在花了几个小时之后,LambdaFinally有什么东西可以做,这对我来说很有用。另外,这是模拟/存根最通用的东西,因为它将覆盖所有aws服务。不知道为什么它没有得到足够的认可。伙计,你应该为此获得一枚奖章。太棒了!JSON定义无疑是个问题。我找不到存根SQS原型的方法,因为函数只绑定到实例。