Abirami
Abirami

Reputation: 303

To mock AWS SES with Sinon

I'm trying to mock SES with Sinon, but facing below error. Tried using aws-sdk-mock, but it's not working.

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

Code snippet of test class:

import * as AWS from 'aws-sdk';

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

Actual class:

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

Is there any way to mock SES with Sinon or with aws-sdk-mock?

Upvotes: 4

Views: 4656

Answers (6)

Derek Lu
Derek Lu

Reputation: 116

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

Upvotes: 0

artemisja9
artemisja9

Reputation: 26

I was able to use awk-sdk-mock by doing the following:

test class

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

Actual class

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

Upvotes: 0

Steve Goossens
Steve Goossens

Reputation: 1028

My answer here is not a direct solution for SES, but it is a working solution I'm using for mocking DynamoDB.DocumentClient and SQS. Perhaps you can adapt my working example for SES and other aws-sdk clients in your unit tests.

I just spent hours trying to get AWS SQS mocking working, without resorting to the aws-sdk-mock requirement of importing aws-sdk clients inside a function.

The mocking for AWS.DynamoDB.DocumentClient was pretty easy, but the AWS.SQS mocking had me stumped until I came across the suggestion to use rewire.

My lambda moves bad messages to a SQS FailQueue (rather than letting the Lambda fail and return the message to the regular Queue for retries, and then DeadLetterQueue after maxRetries). The unit tests needed to mock the following SQS methods:

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

I'll try to keep this example code as concise as I can while still including all the relevant parts:

Snippet of my 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

Abridged Lambda event records (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:[email protected]",
                "ApproximateFirstReceiveTimestamp": "1536763724618"
            },
            "messageAttributes": {},
            "md5OfBody": "e5b16f3a468e6547785a3454cfb33293",
            "eventSource": "aws:sqs",
            "eventSourceARN": "arn:aws:sqs:eu-west-1:123456789012:sqs-queue-name",
            "awsRegion": "eu-west-1"
        }]
    }
}

Abridged unit test file (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"}');
                });
        });
    });
});

Upvotes: 1

sai anudeep
sai anudeep

Reputation: 1265

Using require & without using prototype. This is working for me for mocking 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: []
        };
      }
    };
  }
});

Packages:

"aws-sdk": "^2.453.0"

"sinon": "^7.3.2"

Upvotes: 0

Brian Adams
Brian Adams

Reputation: 45780

The error seems to indicate that AWS is being imported as undefined.

It might be that your ES6 compiler isn't automatically turning this line:

import AWS from 'aws-sdk';

...into an import of everything in aws-sdk into AWS.

Change it to this:

import * as AWS from 'aws-sdk';

...and that may fix the issue.


(Disclaimer: I can't reproduce the error in my environment which is compiling with Babel v7 and automatically handles either approach)

Upvotes: 0

Ankit Agarwal
Ankit Agarwal

Reputation: 30739

You need to use prototype in AWS to stub it:

import AWS from 'aws-sdk';

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

Upvotes: 0

Related Questions