Using Manual Mocks to test the AWS SDK with Jest

Anytime you build Node applications it is highly suggested that your cover your code with tests.  When your code interacts with 3rd party API’s such as AWS you will most certainly want to mock/stub your calls in order to prevent external calls (if you actually want to do external calls, these are called integration tests not unit tests.

If you are using Jest, one solution is utilize the built in support for manual mocks.  I have found the usage of manual mocks invaluable while testing 3rd party API’s such as the AWS.  Keep in mind just because I am using manual mocks this will remove the need for using libraries like SinonJs (a JavaScript framework for creating stubs/mocks/spies).

The way that manual mocks work in Jest is as follows (from the Jest website’s documentation).

Manual mocks are defined by writing a module in a __mocks__/ subdirectory immediately adjacent to the module. For example, to mock a module called user in the models directory, create a file called user.js and put it in the models/__mocks__ directory. Note that the __mocks__ folder is case-sensitive, so naming the directory __MOCKS__ will break on some systems. If the module you are mocking is a node module (eg: fs), the mock should be placed in the __mocks__ directory adjacent to node_modules (unless you configured roots to point to a folder other than the project root).

In my case I want to mock out the usage of the AWS-SDK for Node.

To do this I created a __mocks__ folder at the root of my solution.  I then created a aws-sdk.js file inside this folder.

Now that I have my mocks folder created with a aws-sdk.js file I am able to consume my manual mock in my jest test by simply referencing the aws-sdk via a require('aws-sdk') command.

const AWS = require('./aws-sdk');

With declaration of AWS above my code is able to a use the NPM package during normal usage, or my aws-sdk.js mock when running under the Jest context.

Below is a small sample of the code I have inside my aws-sdk.js file for my manual mock.

const stubs = require('./aws-stubs');
const AWS = {};

// This here is to allow/prevent runtime errors if you are using 
//   AWS.config to do some runtime configuration of the library.
// If you do not need any runtime configuration you can omit this.
AWS.config = {
    setPromisesDependency: (arg) => {}
};

AWS.S3 = function() {

}

// Because I care about using the S3 service's which are part of the SDK
//  I need to setup the correct identifier.
// 
AWS.S3.prototype = {
    ...AWS.S3.prototype,

    // Stub for the listObjectsV2 method in the sdk
    listObjectsV2(params){
        const stubPromise = new Promise((resolve, reject) => {
            // pulling in stub data from an external file to remove the noise
            // from this file.  See the top line for how to pull this in
            resolve(stubs.listObjects);
        });

        return {
            promise: () => {
                return stubPromise;
            }
        }
    }
};

// Export my AWS function so it can be referenced via requires
module.exports = AWS; 

A few things to point out in the code above.

  1. I chose to use the promises implementation of the listObjectsV2.  Because of this I need to return a promise method as my result on my listObjectsV2 function.  I am sure there are other ways to accomplish this, but this worked and is pretty easy.
  2. My function is returning stub data, but this data is described in a separate file called aws-stubs.js which sites along side of my aws-sdk.js file.  I went this route to remove the noise of having the stub data inside my aws-adk file.  You can see a full example of this here.

Now that I have everything setup my tests will no longer attempt to hit the actually aws-sdk, but when running in non-test mode they will.

Till next time,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s