How to mock AWS SDK with Jest

Rita {FlyNerd} Lyczywek - Jun 24 '22 - - Dev Community

From me to future me - how to write unit test for AWS SDK S3 presigned url with Jest

Dear Journal 📖

From me to future me: how to write unit test for AWS SDK S3 by example (presigned url with Jest).

I hate mocks.
That's why I always forgot how to use them when I need them.

Oh Rita, have you tried to spyOn AWS SDK service again? 🤦‍♀️

🛑 STOP

You need to either:

  • mock element on the object (when you have an object)
  • mock whole file (@aws-sdk/s3-request-presigner). I do hate that, but I guess you don't have that much choice, unless you want to wrap it in something else 🤷‍♂️

This is one of the reasons OOP and dependency injection (even manual one) is better (personal preference). I can define unit, its dependencies and then in tests I simply deliver something that fulfils the contract. No need to overwrite objects, files, etc. 🚫 No mocks.

But sometimes there's no other option.

Let's say I have a javascript or typescript file.

// handler.ts

import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { GetObjectCommand } from '@aws-sdk/client-s3';

export const handler: Handler = async () => {
  /* do stuff */
  const command = new GetObjectCommand(input);
  const url = await getSignedUrl(client, command, { expiresIn: 1200 });
  /* do other stuff */
}

Enter fullscreen mode Exit fullscreen mode

Then in spec file:

  • create an mock of whole the module
  • use empty mock function as you wish
// handler.spec.ts

jest.mock('@aws-sdk/s3-request-presigner');
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

jest.mock('@aws-sdk/client-s3');
import { GetObjectCommand } from '@aws-sdk/client-s3';

import { handler } from './handler';

test('when sth do sth', async () => {
  const expectedInput = { ... };
  await handler();
  expect(GetObjectCommand).toHaveBeenCalledWith(expectedInput);
  expect(getSignedUrl).toHaveBeenCalled();
});

Enter fullscreen mode Exit fullscreen mode

Honestly, it doesn't test this function. The fact that sth was called is not enough to confirm that expected behavior happened. It comes to the absurd: to get "real" behavior I need to mock the getSignerUrl() response.

So, I come to the point, where I write the mock, to test the function response, which is response from another mock, but technically is the nearest simulation of the function response and expected output.

// handler.spec.ts

jest.mock('@aws-sdk/s3-request-presigner');
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

jest.mock('@aws-sdk/client-s3');
import { GetObjectCommand } from '@aws-sdk/client-s3';

import { handler } from './handler';
describe('GetUrl', () => {
  const getSignedUrlMock: jest.Mock = getSignedUrl as any; // calm down TypeScript screaming about types

  test('when sth do sth', async () => {
    getSignedUrlMock.mockResolvedValue('example-url.com');
    const expectedInput = { ... };
    const response = await handler();

  expect(GetObjectCommand).toHaveBeenCalledWith(expectedInput);
    expect(response).toEqual({
      statusCode: 200,
      body: JSON.stringify({ url: 'example-url.com' }),
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

Hope, that next time you will thank yourself for this

Sincerely yours
Rita

. . . . . . . . . . .
Terabox Video Player