Lycaon
Lycaon

Reputation: 103

Mocking a module with Jest not working properly

I am trying to mock a module, here S3 from aws-sdk.

The only way I could make it work was like that:

jest.mock('aws-sdk', () => {
  return {
    S3: () => ({
      putObject: jest.fn()
    })
  };
});

The issue with that is that I don't have access to the S3 or putObject as a mock variable that I can check if called for testing purpose.

So I would like to do something like that:

const putObject = jest.fn();

jest.mock('aws-sdk', () => {
  return { S3: () => ({ putObject }) };
});

I always do that for other modules and it works perfectly, it even works for lambda, but not for this case.

To me, the two codes look exactly the same so I really don't understand what is going on and why it does not work exactly the same way.

When I console log the s3 in the code that I want to test, I get that:

{ putObject: undefined }

Also, I use TypeScript so I can't just import the modules in the test file and mockReturnValue it if it's not a mock variable.

Thanks for your help!

EDIT:

Here is a reproducible minimal example of the problem:

uploadFile.ts

import AWS from 'aws-sdk';

const s3 = new AWS.S3();

export const uploadFile = async (key, body) => {
  try {
    await s3
      .putObject({
        Bucket: 'myBucket',
        Key: key,
        Body: body,
      })
      .promise();
  } catch (error) {
    console.log(error);
  }
};

uploadFile.test.ts

import { uploadFile } from './uploadFile';

const mockPutObject = jest.fn(() => ({ promise: jest.fn() }));

jest.mock('aws-sdk', () => {
  return { S3: () => ({ putObject: mockPutObject }) };
});

describe('Test uploadFile', () => {
  it('should call putObject', () => {
    uploadFile('key', { test: 'test' });
    expect(mockPutObject).toHaveBeenCalled();
  });
});

Upvotes: 2

Views: 2533

Answers (1)

Sirode
Sirode

Reputation: 587

I was able to make it work this way:

import * as AWS from 'aws-sdk';
import { uploadFile } from '.';

const mockPutObject = jest.fn().mockImplementation((data) => {
    return {
        promise: () => jest.fn()
    }
});

jest.mock('aws-sdk', () => {
    return {
        S3: function () {
            return {
                putObject: (data: any) => mockPutObject(data)
            }
        }
    };
});

describe('Test uploadFile', () => {
    it('should call putObject', () => {
        uploadFile('key', { test: 'test' });
        expect(mockPutObject).toHaveBeenCalledTimes(1);
        expect(mockPutObject).toHaveBeenCalledWith({
            Bucket: 'myBucket',
            Key: 'key',
            Body: {
                test: 'test'
            },
        });
    });
});

There are a few challenges that you might not have:

  • AWS.S3 is a class that has a constructor and I couldn't find a tsconfig or babel config that worked. Therefore I replaced some of the () => {...} to function () { ... }
  • The way jest hoist the properties make it tricky to return the mockPutObject as it is not yet initialized when it is building the mock
  • The mock needed to return the promise() mock as well to work with your index.ts

Upvotes: 4

Related Questions