Mock AWS services for NodeJS unit tests - node.js

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?

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

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.

Related

Change region of firebase cloud functions of callable in v2

Hey so I am trying to upgrade to v2 of firebase cloud functions, but when trying to change the code I noticed that my functions do not have .region anymore like in v1.
Here the v1 version where I could call .region and change it
import * as functions from "firebase-functions";
exports.helloWorld = functions.region("europe-west1").https.onCall(() => {
functions.logger.info("Hello logs!", { structuredData: true });
return { text: "Hello from Firebase!" };
});
now I upgraded to v2, but I get:
Property 'region' does not exist on type
'typeof import("/.../node_modules/firebase-functions/lib/v2/index")
Trying to achieve something like this for v2 of firebase cloud functions any ideas ?
import { https, logger } from "firebase-functions/v2";
import * as functions from "firebase-functions/v2";
// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript
//
const regionalFunctions = functions.region("europe-west1");
exports.helloWorld = regionalFunctions.https.onCall(() => {
logger.info("Hello logs!", { structuredData: true });
return { text: "Hello ${process.env.PLANET} and ${process.env.AUDIENCE}" };
});
You can specify the region in the function's options as shown below:
import { onCall } from "firebase-functions/v2/https";
export const testFunction = onCall({ region: "..." }, (event) => {
// ...
})

ecs.runTask not executing in Lambda

I have a lambda function that is supposed to start an ecs task when invoked. It gets all the way down to the "Starting execution..." log then it logs "done.". It seems to just skip right over ecs.runTask(). I have tried getting the returned json output by setting the runtask function to a variable, but that has not helped. I have also tried changing some of my parameters and that has not worked as well.
const AWS = require('aws-sdk');
var ecs = new AWS.ECS()
exports.handler = async (event) => {
var params = {
cluster: "ec2-cluster",
enableECSManagedTags: true,
launchType: "FARGATE",
count: 1,
platformVersion: 'LATEST',
networkConfiguration: {
awsvpcConfiguration: {
assignPublicIp: "ENABLED",
securityGroups: [ "sg" ],
subnets: [ "subnet" ]
}
},
startedBy: "testLambda",
taskDefinition: "definition"
}
console.log("Starting execution...");
ecs.runTask(params, function(err, data) {
console.log(err, data)
});
// console.log(myReturn)
console.log("done.")
}
When I run this locally everything works great. When I run this in lambda however it does not start my task.
In your case, you will need to add Promise to ecs.runTask(). In the AWS Lambda documentation, they mentioned that we have to
Make sure that any background processes or callbacks in your code are complete before the code exits.
meaning that we need to await for the ecs.runTask() process to resolve. Here is an example of how we can apply async/await on aws-sdk. From the references above, the way to make your code work would be:
const AWS = require('aws-sdk');
var ecs = new AWS.ECS()
exports.handler = async (event) => {
var params = {
cluster: "ec2-cluster",
enableECSManagedTags: true,
launchType: "FARGATE",
count: 1,
platformVersion: 'LATEST',
networkConfiguration: {
awsvpcConfiguration: {
assignPublicIp: "ENABLED",
securityGroups: [ "sg" ],
subnets: [ "subnet" ]
}
},
startedBy: "testLambda",
taskDefinition: "definition"
}
console.log("Starting execution...");
// Added promise here
await ecs.runTask(params).promise();
// console.log(myReturn)
console.log("done.")
This is a common mistake that we might make when we first get into Lambda, especially when we try to make our Lambda functions work with some other AWS services via aws-sdk.

Does aws-sdk-mock support mocking of AWS SSM (Parameter Store)?

I am trying to mock AWS SSM using aws-sdk-mock with the code below but not working. Does not throw error, fetch the values from Actual store when getParametersByPath is called.
I had a look at the aws-sdk-mock documentation but does not seem to have an example for mocking ssm, is it supported or not.
AWSMock.mock('SSM', 'getParametersByPath', (params, callback) => {
callback(null, mockResponse);
});
I ran across this when trying to do a similar operation: When trying to mock SSM functionality the resources were still attempting to make requests to AWS and were not using the mock functionality.
Example:
import { mock } from 'aws-sdk-mock';
import { SSM } from 'aws-sdk';
import { GetParameterRequest, GetParameterResult } from 'aws-sdk/clients/ssm';
import 'mocha'
...
const ssm: SSM = new SSM();
mock('SSM', 'getParameter', async (request: GetParameterRequest) => {
return { Parameter: { Value: 'value' } } as GetParameterResult;
})
const request: GetParameterRequest = { Name: 'parameter', WithDecryption: true};
const result: GetParameterResult = await ssm.getParameter(request).promise();
expect(result.Parameter.Value).to.equal('value');
...
The error occurred when making the call to getParameter.
Turns out that the reason for our error was that we were instantiating the integration prior to declaring our mock. So the fix was to switch the order of execution and declare the mock before instantiating the integration.
Example:
import { mock } from 'aws-sdk-mock';
import { SSM } from 'aws-sdk';
import { GetParameterRequest, GetParameterResult } from 'aws-sdk/clients/ssm';
import 'mocha'
...
mock('SSM', 'getParameter', async (request: GetParameterRequest) => {
return { Parameter: { Value: 'value' } } as GetParameterResult;
});
// -> Note the following line was moved below the mock declaration.
const ssm: SSM = new SSM();
const request: GetParameterRequest = { Name: 'parameter', WithDecryption: true};
const result: GetParameterResult = await ssm.getParameter(request).promise();
expect(result.Parameter.Value).to.equal('value');
...

context.done called twice within handler 'graphql'

Attempting to create a project based off https://github.com/serverless/serverless-graphql/blob/master/app-backend/dynamodb/handler.js. The code works well, but for some reason, I always get a log warning telling me context.done called twice.
import { graphqlLambda, graphiqlLambda, LambdaHandler } from 'apollo-server-lambda'
import lambdaPlayground from 'graphql-playground-middleware-lambda'
import { makeExecutableSchema } from 'graphql-tools'
import { resolvers } from './resolvers'
const typeDefs = require('./schema.gql')
const schema = makeExecutableSchema({ typeDefs, resolvers, logger: console })
export const graphqlHandler: LambdaHandler = async (event, context) => {
const handler = graphqlLambda({ schema })
return handler(event, context, (error: Error | undefined, output: any) => {
output.headers['Access-Control-Allow-Origin'] = '*'
context.done(error, output)
})
}
export const playgroundHandler = lambdaPlayground({
endpoint: '/graphql',
})
export const graphiqlHandler: any = graphiqlLambda({
endpointURL: '/graphql',
})
This code gives me the following result:
Serverless: POST /graphql (λ: graphql)
Serverless: [200] {"statusCode":200,"headers":{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"},"body":"{\"data\":{\"getUserInfo\":\"ads\"}}"}
Serverless: Warning: context.done called twice within handler 'graphql'!
What is even more strange is that if I comment the context.done call, I get the following output (the call stalls as expected):
Serverless: POST /graphql (λ: graphql)
Serverless: Warning: context.done called twice within handler 'graphql'!
I ran into a similar issue. Try remove async in your function if you are not going to use await. It looks like the function waits until you invoke callback or context.done when async is not there. But it runs through all the way with the async keyword. When it runs through though, whatever it returns at the end of your function is calling the context.done. Here is a sample working code.
Let me also share two useful resources about callback and context from aws lambda.
export const handler = (
{
headers,
pathParameters: pathParams,
queryStringParameters: queryParams,
body,
},
context,
callback
) => {
context.callbackWaitsForEmptyEventLoop = false;
const data = JSON.parse(body);
const params = {
TableName: process.env.EVENT_TABLE,
Item: {
id: uuid.v4(),
title: data.title,
creationDate: new Date().getTime(),
},
};
dynamoDb.put(params, (err, data) => {
if (err) {
callback(err);
}
callback(null, {
statusCode: 200,
body: JSON.stringify(data),
});
});
};
This appears to be already reported: https://github.com/dherault/serverless-offline/issues/405

How to mock module function using proxyquire

I need to mock 'mkdirp-promise' node module which exposes a constructor function as below
mkdirpPromise(dirPath)
.then(() => {
console.log('ABCDEFGH');
resolve(dirPath);
})
.catch((error) => {
console.log('HeABCDEFGHre');
const details = error.message;
const err = customError.failed_create_downloads_directory()
.withDetails(details);
reject(err);
});
Im able to mock it using proxiquire as below for the first time:-
let mkdirpPromiseMock = sinon.stub().rejects();
const sthreeDownloadMock =
proxyquire('./../../modules/sThreeDownload', {
joi: joiMock,
fs: fsMock,
'#monotype/core-error': {
errors: {
ApiError: customErrorMock,
},
},
'aws-sdk': awsSDK,
'mkdirp-promise': mkdirpPromiseMock,
path: pathMock,
});
Now i want to override mkdirpPromiseMock in 2nd test case with
mkdirpPromiseMock = sinon.stub().resolves();
which im not able to. Any help is appreciated.
Proxyquire is not compatible with jest.
You need to use a mocking library like rewiremock.
Please have a look at this answer which goes into detail.
REPL example

Resources