8

Jest 25.3.0

I am trying to mock DynamoDB dependency in my unit tests as follows:

const { findById } = require('./mymodule');

const mockDynamoDB = { getItem: jest.fn() };
jest.mock('aws-sdk', () => ({
  DynamoDB: jest.fn(() => mockDynamoDB)
}));

describe('.', () => {
  it('..', () => {
    findById('test');
    expect(mockDynamoDB.getItem).toBeCalledWith({
      TableName: 'table-name',
      Key: {
        id: { S: 'test' }
      }
    });
  });
});

Unfortunately, when I do that, I get the following error:

ReferenceError: Cannot access 'mockDynamoDB' before initialization

Strangely, if I do this, I can avoid the ReferenceError:

const mockGetItem = { promise: jest.fn() };
jest.mock('aws-sdk', () => ({
  DynamoDB: jest.fn(() => ({
    getItem: jest.fn(() => mockGetItem)
  })
}));

but this doesn't suit my test, as I can't validate the params passed to the getItem function.

The actual code under test is fairly simple, it looks something like this:

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

const dynamodb = new AWS.DynamoDB({apiVersion: '2012-08-10'});

const toRecord = (item) => ({
  id: item.id.S,
  name: item.name.S
});

const findById = (id) => (
  dynamodb.getItem({
    TableName: 'table-name',
    Key: {
      id: { S: id }
    }
  }).promise()
    .then(result => toRecord(result.Item))
    .catch(error => console.log(error)
);

module.exports = {
  findById
}

If anyone has seen this before, or can shed some light on why the first example fails while the second works, it would really help me out. Thank you.

grigori
  • 1,409
  • 3
  • 16
  • 24

2 Answers2

10

Here is the unit test solution:

index.js:

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

const dynamodb = new AWS.DynamoDB({ apiVersion: '2012-08-10' });
const toRecord = (item) => ({
  id: item.id.S,
  name: item.name.S,
});

const findById = (id) =>
  dynamodb
    .getItem({
      TableName: 'table-name',
      Key: {
        id: { S: id },
      },
    })
    .promise()
    .then((result) => toRecord(result.Item))
    .catch((error) => console.log(error));

module.exports = { findById };

index.test.js:

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

jest.mock('aws-sdk', () => {
  const mDynamoDB = { getItem: jest.fn().mockReturnThis(), promise: jest.fn() };
  return { DynamoDB: jest.fn(() => mDynamoDB) };
});

describe('61157392', () => {
  let dynamodb;
  beforeAll(() => {
    dynamodb = new AWS.DynamoDB();
  });
  afterAll(() => {
    jest.resetAllMocks();
  });
  it('should pass', async () => {
    dynamodb.getItem().promise.mockResolvedValueOnce({
      Item: {
        id: { S: '1' },
        name: { S: 'a' },
      },
    });
    const actual = await findById('1');
    expect(actual).toEqual({ id: '1', name: 'a' });
    expect(dynamodb.getItem).toBeCalledWith({
      TableName: 'table-name',
      Key: {
        id: { S: '1' },
      },
    });
    expect(dynamodb.getItem().promise).toBeCalledTimes(1);
  });
});

unit test results with 100% coverage:

 PASS  stackoverflow/61157392/index.test.js (8.216s)
  61157392
    ✓ should pass (4ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |   88.89 |      100 |      75 |    87.5 |                   
 index.js |   88.89 |      100 |      75 |    87.5 | 19                
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        9.559s

source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61157392

Lin Du
  • 88,126
  • 95
  • 281
  • 483
  • Hi @slideshowp2, this looks great but I've hit an issue because in my code I've got something like this `dynamodb.getItem(params).promise().then(onSuccess).catch(onError)`. Using your approach, I'm getting `TypeError: Cannot read property 'then' of undefined`. (I've updated my example above, just to be clearer). Thanks for your help. – grigori Apr 13 '20 at 06:57
  • @grigori Updated answer based on your update. It works fine. – Lin Du Apr 13 '20 at 07:07
  • you're right, I had a typo. Thanks a lot, this helped heaps! – grigori Apr 13 '20 at 09:37
-1

The main problem is:

Since your mymodule is imported before you declare the mock variable, it runs the mock snippet before that as well. That's why it claims your variable wasn't declared yet - 'cause when the mocked code is ran, your environment couldn't find the variable declaration yet, even though you - visually - see it declared before your mock snippet.

Another way of avoiding this error is by doing this:

const {DynamoDB} from 'aws-sdk';

jest.mock('aws-sdk', () => ({
  DynamoDB: () => ({ getItem: jest.fn() })
}));

// At this point, whenever you reference DynamoDB, it's gonna be the mocked version.
// expect(DynamoDB.getItem).toHaveBeenCalled();
Stanley Sathler
  • 368
  • 2
  • 12
  • In other cases jest will complain that DynamoDB should be mocked with a function prefixed with 'mock'. So, this doesn't seem like a legit solution. Jest just seems buggy. I've done this in one file and it runs, then in another file it complains. – amr ras May 12 '21 at 22:02