
I know the title is a quite a mouthful, but if you are trying to run tests on your Lambda functions that interact with AWS Services using the aws-sdk
node module, then you’ve probably run into an issue stubbing or mocking the requests. In this post we’ll learn how to stub different AWS Services with Sinon.JS so that you can properly test your scripts.
UPDATE: AWS Lambda now supports Node v8.10, so we can use async/await
instead of promises. The examples below still work with either v6.10 or v8.10, however, I recommend switching to async/await
as they are more compact than promises. Read my post How To: Stub “.promise()” in AWS-SDK Node.js to learn how to deal with the .promise()
method on aws-sdk services.
Let’s say you have a Lambda function that interacts with AWS’s SQS (Simple Queue Service). v6.10 of Node doesn’t support async/await
, so you will most likely use promises if you don’t want to transpile your code or deal with callback hell. This means you need to Promisify an instance of the AWS SQS service. This is easy enough with:
1 2 3 4 |
const AWS = require('aws-sdk') // AWS SDK const Promise = require('bluebird') // Promise library const SQS = Promise.promisifyAll(new AWS.SQS()) |
Now you could stub SQS’s promisified methods with Sinon.js like this:
1 2 |
const sinon = require('sinon') let stub = sinon.stub(SQS,'receiveMessageAsync') |
However, your code is most likely more complicated than that. If you have several reusable modules (maybe a message manager, for example) that can be used by multiple functions, then this becomes a problem. We can’t stub the AWS SDK until after it gets instantiated. If our test scripts try to stub SQS before it is instantiated by your message manager module (for instance), then it will throw an exception.
The solution is really simple
Since Node caches all of your module calls (any time you require
something), all you need to do is wrap your SQS instantiation and promisification into a separate module like so:
1 2 3 4 5 6 7 |
'use strict' const AWS = require('aws-sdk') // AWS SDK const Promise = require('bluebird') // Promise library // Export promisified SQS library module.exports = Promise.promisifyAll(new AWS.SQS()) |
Name it something like “sqs-service.js” and then simply require
it in any module that needs it. Then when you require it in your test script, you can stub it like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// Require AWS and your SQS wrapper const AWS = require('aws-sdk') const SQS = require('./sqs-service') // Require serverless-mocha-plugin const mochaPlugin = require('serverless-mocha-plugin') const expect = mochaPlugin.chai.expect // Require Sinon.js library const sinon = require('sinon') // Create wrapped Lambda function let wrapped = mochaPlugin.getWrapper('yourFunction', '/path/to/yourFunction.js', 'handler') // Start tests describe('yourFunction', () => { it('implement tests here', () => { // Stub receiveMessageAsync let stub = sinon.stub(SQS,'receiveMessageAsync') // Add all your stubbing stuff here stub.resolves({ message: 'message from SQS' }) return wrapped.run({}).then((response) => { expect(response).to.not.be.empty }) }) }) |
Now this version of SQS
gets cached because it was require
d before any other modules were called. All subsequent modules will use this stubbed version allowing you to create extremely complex tests that can return any response you’d like from the AWS service. Pretty sweet!
NOTE: The test above is using the serverless-mocha-plugin to wrap Lambda functions that use the Serverless framework. This allows you to simulate running your Lambda functions with environment variables and different events and then run assertions on the results.
Are you using MySQL, Redis, or other databases in your Lambda project? Learn how to Reuse Database Connections in AWS Lambda.
Tags: amazon web services, aws lambda, javascript, mocha, nodejs, promises, serverless, sinon.js, test-driven development
Did you like this post? 👍 Do you want more? 🙌 Follow me on Twitter or check out some of the projects I’m working on.
Thanks for the article. I’ve used the similar approach to test lambda function with serverless-mocha-plugin and it is great to test in local. However, can you make the same test to work against live AWS service? If so, what is required to set to run it against AWS service? Attempting to do it, I get some error and would like to hear how you do it.
Are you trying to stub AWS services in a serverless-mocha-plugin test running against a live Lambda function? That wouldn’t be possible since it invokes the remote Lambda function in the AWS environment. Locally you can inject dependencies and the Node environment will cache them. When invoking a live Lambda, it just sends the
event
and then you can test the results.When I write end-to-end tests for live functions, I typically test for basic connectivity to dependent services. My other local tests should cover all the more complex functionality. For example, if I have a function that uses an SQS queue and RDS, I’d send in a test event that required those services, then just test against a failure. If those services weren’t easily exposed, I might add a route or an event that would check connectivity to those services. I often do this with connections that you can freeze, like MySQL or Redis. That way I can use the same test to check how many times a function has been reused.
Thanks for your reply. Since I saw the option of –stage and –region in serverless-mocha-plugin, I thought it might be possible. However, I just found out that it’s not possible and serverless-mocha-plugin also noted that live Lambda function is not supported yet.
What you described are the approaches I was thinking but was not sure I was going to the right direction.
I have functional tests which tests lots of possible inputs against Lambda functions locally. Now, I’ll create another tests simply invoking live lamda in AWS and check out the result as well as end to end test using HTTP Gateway.
I’m trying this same exact code, promisfying my S3 service and stubbing listObjectsV2Async. But I keep getting this error – MissingRequiredParameter: Missing required key ‘Bucket’ in params.
const AWS = require(‘aws-sdk’) // AWS SDK
const Promise = require(‘bluebird’) // Promise library
// Export promisified S3 library
module.exports = Promise.promisifyAll(new AWS.S3())
const stubbedList = sinon.stub(s3, ‘listObjectsV2Async’);
stubbedList.withArgs({ Bucket: ‘my-test-bucket’, Prefix: ‘my/prefix’ }).resolves(myReturnObj);
const result = await myAsyncFunc(); // The real call to listObjectsV2Async happens in here and keeps throwing that error
Any suggestions?
Hi Andrew,
It’s hard to tell without seeing the full code, but the error you’re getting is coming from S3, not your stub. It looks like your call to
listObjectsV2Async
isn’t being stubbed correctly in yourmyAsyncFunc()
. Try logging the “s3.listObjectsV2Async” function and make sure that it is actually getting stubbed.– Jeremy