DEV Community

Rita {FlyNerd}
Rita {FlyNerd}

Posted on • Updated on

How to mock AWS SDK 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 do not 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

Discussion (0)