Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/dwyl/aws-sdk-mock

:rainbow: AWSomocks for Javascript/Node.js aws-sdk tested, documented & maintained. Contributions welcome!
https://github.com/dwyl/aws-sdk-mock

Last synced: about 2 months ago
JSON representation

:rainbow: AWSomocks for Javascript/Node.js aws-sdk tested, documented & maintained. Contributions welcome!

Awesome Lists containing this project

README

        

# aws-sdk-mock

AWSome mocks for `Javascript` `aws-sdk` services.

[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/dwyl/aws-sdk-mock/ci.yml?label=build&style=flat-square&branch=main)](https://github.com/dwyl/aws-sdk-mock/actions/workflows/ci.yml)
[![codecov.io](https://img.shields.io/codecov/c/github/dwyl/aws-sdk-mock/main.svg?style=flat-square)](http://codecov.io/github/dwyl/aws-sdk-mock?branch=main)
[![Known Vulnerabilities](https://snyk.io/test/github/dwyl/aws-sdk-mock/badge.svg?targetFile=package.json&style=flat-square)](https://snyk.io/test/github/dwyl/aws-sdk-mock?targetFile=package.json)
[![npm package version](https://img.shields.io/npm/v/aws-sdk-mock.svg?style=flat-square&color=bright-green)](https://www.npmjs.com/package/aws-sdk-mock)
[![Node.js Version](https://img.shields.io/node/v/aws-sdk-mock.svg?style=flat-square "Node.js 18.x, 20.x & 21.x supported")](http://nodejs.org/download/)
![npm monthly downloads](https://img.shields.io/npm/dm/aws-sdk-mock?style=flat-square)
[![HitCount](https://hits.dwyl.com/dwyl/aws-sdk-mock.svg?style=flat-square)](http://hits.dwyl.com/dwyl/aws-sdk-mock)
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat-square)](https://github.com/dwyl/aws-sdk-mock/issues)

This module was created to help test AWS Lambda functions but can be used in any situation where the AWS SDK needs to be mocked.

This library is best suited for `AWS SDK for Javascript (v2)` - see the [introductory post on the AWS blog](https://aws.amazon.com/blogs/developer/mocking-modular-aws-sdk-for-javascript-v3-in-unit-tests/) for more context.
If you are using `AWS SDK v3` you might not _need_ this library, see:
[aws-sdk-mock/issues#209](https://github.com/dwyl/aws-sdk-mock/issues/209#issuecomment-764841699)

If you are *new* to Amazon WebServices Lambda
(*or need a refresher*),
please checkout our our
***Beginners Guide to AWS Lambda***:

* [Why](#why)
* [What](#what)
* [Getting Started](#how)
* [Documentation](#documentation)
* [Background Reading](#background-reading)

## Why?

Testing your code is *essential* everywhere you need *reliability*.

Using stubs means you can prevent a specific method from being called directly. In our case we want to prevent the actual AWS services to be called while testing functions that use the AWS SDK.

## What?

Uses [Sinon.js](https://sinonjs.org/) under the hood to mock the AWS SDK services and their associated methods.

## Documentation

### `AWS.mock(service, method, replace)`

Replaces a method on an AWS service with a replacement function or string.

| Param | Type | Optional/Required | Description |
| :------------- | :------------- | :------------- | :------------- |
| `service` | string | Required | AWS service to mock e.g. SNS, DynamoDB, S3 |
| `method` | string | Required | method on AWS service to mock e.g. 'publish' (for SNS), 'putItem' for 'DynamoDB' |
| `replace` | string or function | Required | A string or function to replace the method |

### `AWS.restore(service, method)`

Removes the mock to restore the specified AWS service

| Param | Type | Optional/Required | Description |
| :------------- | :------------- | :------------- | :------------- |
| `service` | string | Optional | AWS service to restore - If only the service is specified, all the methods are restored |
| `method` | string | Optional | Method on AWS service to restore |

If `AWS.restore` is called without arguments (`AWS.restore()`) then all the services and their associated methods are restored
i.e. equivalent to a 'restore all' function.

### `AWS.remock(service, method, replace)`

Updates the `replace` method on an existing mocked service.

| Param | Type | Optional/Required | Description |
| :------------- | :------------- | :------------- | :------------- |
| `service` | string | Required | AWS service to mock e.g. SNS, DynamoDB, S3 |
| `method` | string | Required | method on AWS service to mock e.g. 'publish' (for SNS), 'putItem' for 'DynamoDB' |
| `replace` | string or function | Required | A string or function to replace the method |

### `AWS.setSDK(path)`

Explicitly set the require path for the `aws-sdk`

| Param | Type | Optional/Required | Description |
| :------------- | :------------- | :------------- | :------------- |
| `path` | string | Required | Path to a nested AWS SDK node module |

### `AWS.setSDKInstance(sdk)`

Explicitly set the `aws-sdk` instance to use

| Param | Type | Optional/Required | Description |
| :------------- | :------------- | :------------- | :------------- |
| `sdk` | object | Required | The AWS SDK object |

## *How*? (*Usage*)

### *install* `aws-sdk-mock` from NPM

```sh
npm install aws-sdk-mock --save-dev
```

### Use in your Tests

#### Using plain JavaScript

```js

const AWS = require('aws-sdk-mock');

AWS.mock('DynamoDB', 'putItem', function (params, callback){
callback(null, 'successfully put item in database');
});

AWS.mock('SNS', 'publish', 'test-message');

// S3 getObject mock - return a Buffer object with file data
AWS.mock('S3', 'getObject', Buffer.from(require('fs').readFileSync('testFile.csv')));

/**
TESTS
**/

AWS.restore('SNS', 'publish');
AWS.restore('DynamoDB');
AWS.restore('S3');
// or AWS.restore(); this will restore all the methods and services
```

#### Using TypeScript

```typescript
import AWSMock from 'aws-sdk-mock';
import AWS from 'aws-sdk';
import { GetItemInput } from 'aws-sdk/clients/dynamodb';

beforeAll(async (done) => {
//get requires env vars
done();
});

describe('the module', () => {

/**
TESTS below here
**/

it('should mock getItem from DynamoDB', async () => {
// Overwriting DynamoDB.getItem()
AWSMock.setSDKInstance(AWS);
AWSMock.mock('DynamoDB', 'getItem', (params: GetItemInput, callback: Function) => {
console.log('DynamoDB', 'getItem', 'mock called');
callback(null, {pk: 'foo', sk: 'bar'});
})

const input:GetItemInput = { TableName: '', Key: {} };
const dynamodb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
expect(await dynamodb.getItem(input).promise()).toStrictEqual({ pk: 'foo', sk: 'bar' });

AWSMock.restore('DynamoDB');
});

it('should mock reading from DocumentClient', async () => {
// Overwriting DynamoDB.DocumentClient.get()
AWSMock.setSDKInstance(AWS);
AWSMock.mock('DynamoDB.DocumentClient', 'get', (params: GetItemInput, callback: Function) => {
console.log('DynamoDB.DocumentClient', 'get', 'mock called');
callback(null, {pk: 'foo', sk: 'bar'});
});

const input:GetItemInput = { TableName: '', Key: {} };
const client = new AWS.DynamoDB.DocumentClient({apiVersion: '2012-08-10'});
expect(await client.get(input).promise()).toStrictEqual({ pk: 'foo', sk: 'bar' });

AWSMock.restore('DynamoDB.DocumentClient');
});
});
```

#### Sinon

You can also pass Sinon spies to the mock:

```js
const updateTableSpy = sinon.spy();
AWS.mock('DynamoDB', 'updateTable', updateTableSpy);

// Object under test
myDynamoManager.scaleDownTable();

// Assert on your Sinon spy as normal
assert.isTrue(updateTableSpy.calledOnce, 'should update dynamo table via AWS SDK');
const expectedParams = {
TableName: 'testTableName',
ProvisionedThroughput: {
ReadCapacityUnits: 1,
WriteCapacityUnits: 1
}
};
assert.isTrue(updateTableSpy.calledWith(expectedParams), 'should pass correct parameters');
```

**NB: The AWS Service needs to be initialised inside the function being tested in order for the SDK method to be mocked** e.g for an AWS Lambda function example 1 will cause an error `ConfigError: Missing region in config` whereas in example 2 the sdk will be successfully mocked.

Example 1:

```js
const AWS = require('aws-sdk');
const sns = AWS.SNS();
const dynamoDb = AWS.DynamoDB();

exports.handler = function(event, context) {
// do something with the services e.g. sns.publish
}
```

Example 2:

```js
const AWS = require('aws-sdk');

exports.handler = function(event, context) {
const sns = AWS.SNS();
const dynamoDb = AWS.DynamoDB();
// do something with the services e.g. sns.publish
}
```

Also note that if you initialise an AWS service inside a callback from an async function inside the handler function, that won't work either.

Example 1 (won't work):

```js
exports.handler = function(event, context) {
someAsyncFunction(() => {
const sns = AWS.SNS();
const dynamoDb = AWS.DynamoDB();
// do something with the services e.g. sns.publish
});
}
```

Example 2 (will work):

```js
exports.handler = function(event, context) {
const sns = AWS.SNS();
const dynamoDb = AWS.DynamoDB();
someAsyncFunction(() => {
// do something with the services e.g. sns.publish
});
}
```

### Nested services

It is possible to mock nested services like `DynamoDB.DocumentClient`. Simply use this dot-notation name as the `service` parameter to the `mock()` and `restore()` methods:

```js
AWS.mock('DynamoDB.DocumentClient', 'get', function(params, callback) {
callback(null, {Item: {Key: 'Value'}});
});
```

**NB: Use caution when mocking both a nested service and its parent service.** The nested service should be mocked before and restored after its parent:

```js
// OK
AWS.mock('DynamoDB.DocumentClient', 'get', 'message');
AWS.mock('DynamoDB', 'describeTable', 'message');
AWS.restore('DynamoDB');
AWS.restore('DynamoDB.DocumentClient');

// Not OK
AWS.mock('DynamoDB', 'describeTable', 'message');
AWS.mock('DynamoDB.DocumentClient', 'get', 'message');

// Not OK
AWS.restore('DynamoDB.DocumentClient');
AWS.restore('DynamoDB');
```

### Don't worry about the constructor configuration
Some constructors of the aws-sdk will require you to pass through a configuration object.

```js
const csd = new AWS.CloudSearchDomain({
endpoint: 'your.end.point',
region: 'eu-west'
});
```

Most mocking solutions with throw an `InvalidEndpoint: AWS.CloudSearchDomain requires an explicit 'endpoint' configuration option` when you try to mock this.

**aws-sdk-mock** will take care of this during mock creation so you **won't get any configuration errors**!

If configurations errors still occur it means you passed wrong configuration in your implementation.

### Setting the `aws-sdk` module explicitly

Project structures that don't include the `aws-sdk` at the top level `node_modules` project folder will not be properly mocked. An example of this would be installing the `aws-sdk` in a nested project directory. You can get around this by explicitly setting the path to a nested `aws-sdk` module using `setSDK()`.

Example:

```js
const path = require('path');
const AWS = require('aws-sdk-mock');

AWS.setSDK(path.resolve('../../functions/foo/node_modules/aws-sdk'));

/**
TESTS
**/
```

### Setting the `aws-sdk` object explicitly

Due to transpiling, code written in TypeScript or ES6 may not correctly mock because the `aws-sdk` object created within `aws-sdk-mock` will not be equal to the object created within the code to test. In addition, it is sometimes convenient to have multiple SDK instances in a test. For either scenario, it is possible to pass in the SDK object directly using `setSDKInstance()`.

Example:

```js
// test code
const AWSMock = require('aws-sdk-mock');
import AWS from 'aws-sdk';
AWSMock.setSDKInstance(AWS);
AWSMock.mock('SQS', /* ... */);

// implementation code
const sqs = new AWS.SQS();
```

### Configuring promises

If your environment lacks a global Promise constructor (e.g. nodejs 0.10), you can explicitly set the promises on `aws-sdk-mock`. Set the value of `AWS.Promise` to the constructor for your chosen promise library.

Example (if Q is your promise library of choice):

```js
const AWS = require('aws-sdk-mock'),
Q = require('q');

AWS.Promise = Q.Promise;

/**
TESTS
**/
```

## Background Reading

* [Mocking using Sinon.js](http://sinonjs.org/docs/)
* [AWS Lambda](https://github.com/dwyl/learn-aws-lambda)

**Contributions welcome! Please submit issues or PRs if you think of anything that needs updating/improving**