4

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?

Abirami
  • 223
  • 6
  • 21

6 Answers6

1

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: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"
        }]
    }
}

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"}');
                });
        });
    });
});
Steve Goossens
  • 968
  • 1
  • 8
  • 16
  • In my example, the Lambda is index.js, and it has module.exports including the handler function. In the test file, `const lambda = rewire('../index');` is the rewired lambda, which is tested with LambdaTester – Steve Goossens Nov 19 '20 at 11:10
0

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;
  }
});
Ankit Agarwal
  • 30,378
  • 5
  • 37
  • 62
0

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)

Brian Adams
  • 43,011
  • 9
  • 113
  • 111
  • Getting this error now >> TypeError: Cannot stub non-existent own property SES. Tried using aws-sdk-mock, but it's not working and calling the original method. – Abirami Mar 25 '19 at 08:05
0

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"

sai anudeep
  • 1,135
  • 13
  • 26
0

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);
}
artemisja9
  • 26
  • 1
0
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();
Derek Lu
  • 106
  • 1
  • 1