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 */
}
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();
});
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' }),
});
});
});
Hope, that next time you will thank yourself for this
Sincerely yours
Rita
Top comments (3)
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.great to see I could help!
Hey Rita, any idea why i see
TypeError: getSignedUrlMock.mockResolvedValue is not a function
?
the getSignedUrlMock itself is [AsyncFunction: getSignedUrl]