DEV Community

Rita {FlyNerd} Lyczywek
Rita {FlyNerd} Lyczywek

Posted on • Edited on

How to mock AWS SDK with Jest

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

Top comments (3)

Collapse
 
danielransome profile image
danielransome

Hi Rita, this was a real help, thanks! Mocking AWS has been quite a headache for me. I found aws-sdk-client-mock to be a really helpful lib but I don't think it works in this scenario and I've ended up mocking as per your example code.

Collapse
 
ritaly profile image
Rita {FlyNerd} Lyczywek

great to see I could help!

Collapse
 
bym profile image
mat

Hey Rita, any idea why i see
TypeError: getSignedUrlMock.mockResolvedValue is not a function
?
the getSignedUrlMock itself is [AsyncFunction: getSignedUrl]