【问题标题】:To mock AWS SES with Sinon使用 Sinon 模拟 AWS SES
【发布时间】:2019-03-22 12:06:44
【问题描述】:

我正在尝试使用 Sinon 模拟 SES,但遇到以下错误。尝试使用 aws-sdk-mock,但它不起作用。

Error: TypeError: Cannot stub non-existent own property sendEmail

测试类代码sn-p:

import * as AWS from 'aws-sdk';

const sandbox = sinon.createSandbox();
sandbox.stub(AWS.SES, 'sendEmail').returns({promise: () => true});

实际类:

import * as AWS from 'aws-sdk';
import * as _ from 'lodash';    

export async function sendAlertMailOnFailure(status:any)
{   
    // load AWS SES
    var ses = new AWS.SES();   
    const params = {
        Destination: {
          ToAddresses: <to_address>
        },
        Message: {...},    
        Source: <sender_address>
      }
      ses.sendEmail(params, (err, data) => {
        if (err) {
          log.error("Error sending mail::");
          log.error(err, err.stack);
        }
      })
}

有没有办法用 Sinon 或用 aws-sdk-mock 模拟 SES?

【问题讨论】:

  • 在您使用 AWS SES 的位置显示您的业务逻辑代码

标签: node.js aws-sdk sinon amazon-ses aws-sdk-mock


【解决方案1】:

我在这里的回答不是SES 的直接解决方案,但它我用来模拟DynamoDB.DocumentClientSQS 的有效解决方案。也许您可以在单元测试中为SES 和其他aws-sdk 客户端调整我的工作示例。

我只是花了几个小时试图让 AWS SQS 模拟工作,没有诉诸 aws-sdk-mock 要求在函数中导入 aws-sdk 客户端。

AWS.DynamoDB.DocumentClient 的模拟非常简单,但 AWS.SQS 的模拟让我感到困惑,直到我发现使用 rewire 的建议。

我的 lambda 将错误消息移动到 SQS FailQueue(而不是让 Lambda 失败并将消息返回到常规队列进行重试,然后在 maxRetries 之后返回 DeadLetterQueue)。模拟以下 SQS 方法所需的单元测试:

  • SQS.getQueueUrl
  • SQS.sendMessage
  • SQS.deleteMessage

我会尽量保持这个示例代码简洁,同时仍然包含所有相关部分:

我的 AWS Lambda (index.js) 的片段:

const AWS = require('aws-sdk');
AWS.config.update({region:'eu-west-1'});
const docClient = new AWS.DynamoDB.DocumentClient();
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
// ...snip

精简的 Lambda 事件记录 (event.json)

{
    "valid": {
        "Records": [{
            "messageId": "c292410d-3b27-49ae-8e1f-0eb155f0710b",
            "receiptHandle": "AQEBz5JUoLYsn4dstTAxP7/IF9+T1S994n3FLkMvMmAh1Ut/Elpc0tbNZSaCPYDvP+mBBecVWmAM88SgW7iI8T65Blz3cXshP3keWzCgLCnmkwGvDHBYFVccm93yuMe0i5W02jX0s1LJuNVYI1aVtyz19IbzlVksp+z2RxAX6zMhcTy3VzusIZ6aDORW6yYppIYtKuB2G4Ftf8SE4XPzXo5RCdYirja1aMuh9DluEtSIW+lgDQcHbhIZeJx0eC09KQGJSF2uKk2BqTGvQrknw0EvjNEl6Jv56lWKyFT78K3TLBy2XdGFKQTsSALBNtlwFd8ZzcJoMaUFpbJVkzuLDST1y4nKQi7MK58JMsZ4ujZJnYvKFvgtc6YfWgsEuV0QSL9U5FradtXg4EnaBOnGVTFrbE18DoEuvUUiO7ZQPO9auS4=",
            "body": "{ \"key1\": \"value 1\", \"key2\": \"value 2\", \"key3\": \"value 3\", \"key4\": \"value 4\", \"key5\": \"value 5\" }",
            "attributes": {
                "ApproximateReceiveCount": "1",
                "SentTimestamp": "1536763724607",
                "SenderId": "AROAJAAXYIAN46PWMV46S:steve.goossens@bbc.co.uk",
                "ApproximateFirstReceiveTimestamp": "1536763724618"
            },
            "messageAttributes": {},
            "md5OfBody": "e5b16f3a468e6547785a3454cfb33293",
            "eventSource": "aws:sqs",
            "eventSourceARN": "arn:aws:sqs:eu-west-1:123456789012:sqs-queue-name",
            "awsRegion": "eu-west-1"
        }]
    }
}

删节的单元测试文件(test/index.test.js):

const AWS = require('aws-sdk');
const expect = require('chai').expect;
const LamdbaTester = require('lambda-tester');
const rewire = require('rewire');
const sinon = require('sinon');

const event = require('./event');
const lambda = rewire('../index');

let sinonSandbox;

function mockGoodSqsMove() {
    const promiseStubSqs = sinonSandbox.stub().resolves({});
    const sqsMock = {
        getQueueUrl: () => ({ promise: sinonSandbox.stub().resolves({ QueueUrl: 'queue-url' }) }),
        sendMessage: () => ({ promise: promiseStubSqs }),
        deleteMessage: () => ({ promise: promiseStubSqs })
    }
    lambda.__set__('sqs', sqsMock);
}

describe('handler', function () {
    beforeEach(() => {
        sinonSandbox = sinon.createSandbox();
    });

    afterEach(() => {
        sinonSandbox.restore();
    });

    describe('when SQS message is in dedupe cache', function () {
        beforeEach(() => {
            // mock SQS
            mockGoodSqsMove();
            // mock DynamoDBClient
            const promiseStub = sinonSandbox.stub().resolves({'Item': 'something'});
            sinonSandbox.stub(AWS.DynamoDB.DocumentClient.prototype, 'get').returns({ promise: promiseStub });
        });

        it('should return an error for a duplicate message', function () {
            return LamdbaTester(lambda.handler)
                .event(event.valid)
                .expectReject((err, additional) => {
                    expect(err).to.have.property('message', 'Duplicate message: {"Item":"something"}');
                });
        });
    });
});

【讨论】:

  • 在我的示例中,Lambda 是 index.js,它有 module.exports 包括处理函数。在测试文件中,const lambda = rewire('../index'); 是重新连线的 lambda,用 LambdaTester 测试
【解决方案2】:

您需要在AWS 中使用prototype 来存根它:

import AWS from 'aws-sdk';

const sandbox = sinon.createSandbox();
sandbox.stub(AWS.prototype, 'SES').returns({
  sendEmail: () => {
    return true;
  }
});

【讨论】:

  • 得到错误:TypeError:无法读取未定义的属性“原型”
  • 你安装aws-sdk了吗?
  • 是的。它已安装。使用 DynamoDB,它运行良好。 >> sandbox.stub(DynamoDB.DocumentClient.prototype, 'update').returns({promise: () => true});
  • 错误:试图存根未定义的属性“SES”
【解决方案3】:

该错误似乎表明AWS 被导入为undefined

可能是您的 ES6 编译器没有自动转这一行:

import AWS from 'aws-sdk';

...将aws-sdk 中的所有内容导入AWS

改成这样:

import * as AWS from 'aws-sdk';

...这可能会解决问题。


(免责声明:我无法在使用Babel v7 编译并自动处理任一方法的环境中重现错误)

【讨论】:

  • 现在出现此错误>> TypeError:无法存根不存在的自有属性 SES。尝试使用 aws-sdk-mock,但它不起作用并调用原始方法。
【解决方案4】:

使用 require 和不使用原型。这对我模拟 DynamoDB 很有用。

const aws = require('aws-sdk');
const sinon = require('sinon');

const sandbox = sinon.createSandbox();

this.awsStub = sandbox.stub(aws, 'DynamoDB').returns({
  query: function() {
    return {
      promise: function() {
        return {
          Items: []
        };
      }
    };
  }
});

“aws-sdk”:“^2.453.0”

“sinon”:“^7.3.2”

【讨论】:

    【解决方案5】:

    我可以通过执行以下操作来使用 awk-sdk-mock:

    测试类

    const AWSMock = require('aws-sdk-mock');
    const AWS = require('aws-sdk');
    AWSMock.setSDKInstance(AWS);
    
    ...
    
        AWSMock.mock('SES', 'sendRawEmail', mockSendEmail);
    // call method that needs to mock send an email goes below
        sendEmail(to, from, subject, body, callback);
    
    function mockSendEmail(params, callback) {
        console.log('mock email');
        return callback({
            MessageId: '1234567',
        });
    }
    

    实际类

    const aws = require('aws-sdk');
    const nodemailer = require('nodemailer');
    
    
    function sendEmail(to, from, subject, body, callback) {
        let addresses = to;
        if (!Array.isArray(addresses)) {
            addresses = [addresses];
        }
        let replyTo = [];
        if (from) {
            replyTo.push(from);
        }
    
        let data = {
            to: addresses,
            replyTo,
            subject,
            text: body,
        };
    
        nodemailer.createTransport({ SES: new aws.SES({ apiVersion: '2010-12-01' }) }).sendMail(data, callback);
    }
    

    【讨论】:

      【解决方案6】:
      const AWS = require('aws-sdk');
      ...
      const sandbox = sinon.createSandbox();
      sandbox.stub(AWS, 'SES').returns({
          sendRawEmail: () => {
              console.log("My sendRawEmail");
              return {
                  promise: function () {
                      return {
                          MessageId: '987654321'
                      };
                  }
              };
          }
      });
      let ses = new AWS.SES({ region: 'us-east-1' });
      let result = ses.sendRawEmail(params).promise();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-08-12
        • 2019-03-24
        • 2020-08-13
        • 2016-04-09
        • 1970-01-01
        • 2018-06-16
        • 2015-11-10
        • 2017-07-25
        相关资源
        最近更新 更多