How to mock AWS DynamoDB in Jest for Serverless Nodejs Lambda?

Viewed 9958

I wrote a lambda as follows.

handler.js

const aws = require('aws-sdk');
const dynamoDb = new aws.DynamoDB.DocumentClient();

const testHandler = async event => {
  // some code
  // ...
  const user = await getUser(userId)
  // ...
  // some code
}

const promisify = foo => new Promise((resolve, reject) => {
  foo((error, result) => {
    if (error) {
      reject(error)
    } else {
      resolve(result)
    }
  })
})

const getUser = (userId) => promisify(callback =>
  dynamoDb.get({
    TableName: 'test-table',
    Key: {
      "PK": `${userId}`,
      "SK": `${userId}`
    }
  }, callback))
  .then((user) => {
    console.log(`Retrieved user: ${userId}`)
    return user
  })


module.exports = {
  testHandler: testHandler,
  getUser: getUser
}

I want to write a unit test for testing the getUser function so I tried the following.

handler.test.js

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

const dynamoDbGetParameterPromise = jest.fn().mockReturnValue({
  promise: jest.fn().mockResolvedValue({
    PK: 'userId-123', SK: 'userId-123'
  })
})

AWS.DynamoDB.DocumentClient = jest.fn().mockImplementation(() => ({
  get: dynamoDbGetParameterPromise
}))


describe('test getUser', () => {

  beforeEach(() => {
    jest.resetModules()
  });

  test('get user success', async () => {
    const user = { PK: 'userId-123', SK: 'userId-123' };
    const result = await handler.getUser(userId);
    expect(result).toEqual(user);
  });
});

The error is as follows.

ConfigError: Missing region in config

      105 |
      106 | const getUser = (userId) => promisify(callback =>
    > 107 |   dynamoDb.get({
          |            ^
      108 |     TableName: 'test-table',
      109 |     Key: {
      110 |       "PK": 'userId-123',

It seems the test still uses the dynamoDb in the handler.js rather than the mocked in the test.

Any ideas on how to wire up the mock correctly to test the function? Thanks in advance!

2 Answers

You could use jest.mock(moduleName, factory, options) to mock aws-sdk module manually.

E.g.

handler.js:

const aws = require('aws-sdk');
const dynamoDb = new aws.DynamoDB.DocumentClient();

const promisify = (foo) =>
  new Promise((resolve, reject) => {
    foo((error, result) => {
      if (error) {
        reject(error);
      } else {
        resolve(result);
      }
    });
  });

const getUser = (userId) =>
  promisify((callback) =>
    dynamoDb.get(
      {
        TableName: 'test-table',
        Key: {
          PK: `${userId}`,
          SK: `${userId}`,
        },
      },
      callback,
    ),
  ).then((user) => {
    console.log(`Retrieved user: ${userId}`);
    return user;
  });

module.exports = { getUser };

handler.test.js:

const aws = require('aws-sdk');
const { getUser } = require('./handler');

jest.mock('aws-sdk', () => {
  const mDocumentClient = { get: jest.fn() };
  const mDynamoDB = { DocumentClient: jest.fn(() => mDocumentClient) };
  return { DynamoDB: mDynamoDB };
});
const mDynamoDb = new aws.DynamoDB.DocumentClient();

describe('64564233', () => {
  afterAll(() => {
    jest.resetAllMocks();
  });
  it('should get user', async () => {
    const mResult = { name: 'teresa teng' };
    mDynamoDb.get.mockImplementationOnce((_, callback) => callback(null, mResult));
    const actual = await getUser(1);
    expect(actual).toEqual({ name: 'teresa teng' });
    expect(mDynamoDb.get).toBeCalledWith(
      {
        TableName: 'test-table',
        Key: {
          PK: '1',
          SK: '1',
        },
      },
      expect.any(Function),
    );
  });

  it('should handler error', async () => {
    const mError = new Error('network');
    mDynamoDb.get.mockImplementationOnce((_, callback) => callback(mError));
    await expect(getUser(1)).rejects.toThrowError('network');
    expect(mDynamoDb.get).toBeCalledWith(
      {
        TableName: 'test-table',
        Key: {
          PK: '1',
          SK: '1',
        },
      },
      expect.any(Function),
    );
  });
});

unit test result:

 PASS  src/stackoverflow/64564233/handler.test.js (14.929s)
  64564233
    ✓ should get user (23ms)
    ✓ should handler error (3ms)

  console.log src/stackoverflow/64564233/handler.js:433
    Retrieved user: 1

------------|----------|----------|----------|----------|-------------------|
File        |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
------------|----------|----------|----------|----------|-------------------|
All files   |      100 |      100 |      100 |      100 |                   |
 handler.js |      100 |      100 |      100 |      100 |                   |
------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        17.435s

source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/64564233

You can use jest's auto-mock by adding

jest.mock("aws-sdk");

and then AWS.DynamoDB.DocumentClient will be a mocked class so you'll be able to mock it's implementation. And since we want it's get method to be a function that accepts anything as a first argument (as we won't do anything with it within the mock implementation) and a callback that we're expecting it to have been called with null and user we can mock it like this:

AWS.DynamoDB.DocumentClient.prototype.get.mockImplementation((_, cb) => {
  cb(null, user);
});
Related