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]