How To: Stub AWS Services in Lambda Functions using Serverless, Sinon.JS and Promises

If you are trying to run tests on your Lambda functions that interact with AWS Services using the aws-sdk node module, then this post is for you. Learn how to easily stub these services with Sinon.js.

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:

javascript
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:

javascript
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:

javascript
'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:

javascript
// 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 required 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.