jujumbura
jujumbura

Reputation: 423

Mock AWS services for NodeJS unit tests

I am looking at setting up some unit tests for a NodeJS project, but I am wondering how to mock up my usage of AWS services. I am using a wide variety: SNS, SQS, DynamoDB, S3, ECS, EC2, Autoscaling, etc. Does anybody have any good leads on how I might mock these up?

Upvotes: 1

Views: 2253

Answers (2)

madhead
madhead

Reputation: 33412

Take a look at LocalStack. It provides an easy-to-use test/mocking framework for developing AWS-related applications by spinnin up the AWS-compatible APIs on your local machine or in Docker. It supports two dozen of AWS APIs and SQS is among them. It is really a great tool for functional testing without using a separate environment in AWS for that.

Upvotes: 0

Steve Goossens
Steve Goossens

Reputation: 1028

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

Related Questions