I am new to Jest and currently practicing writing tests for my old project. I am stuck on how to write a test for Amazon's S3Client.
const s3 = new S3Client({
credentials: {
accessKeyId: bucketAccess,
secretAccessKey: bucketSecret,
},
region: bucketRegion,
});
export const uploadProductHandler = async (req, res) => {
const files = req.files;
const images = [];
//generateRandom fnc generate random value that can be use as Id for a data
const productId = generateRandom(10);
// uploading files to aws ...
try {
files.forEach(async (file) => {
const imageName = generateRandom(32);
images.push(imageName);
const params = {
Bucket: bucketName,
Key: imageName,
Body: file.buffer,
ContentType: file.mimetype,
};
const commad = new PutObjectCommand(params);
await s3.send(commad);
});
} catch {
return res.sendStatus(502);
}
const data = req.body;
const date = Date.now();
const urls = await setUrls(images);
try {
const product = new Product({ ...data, date: date, images: images, productId: productId, imagesUrl: { urls: urls, generated: date } });
await product.save();
res.sendStatus(201);
} catch (err) {
res.status(400).send(err.message);
}
};
jest.mock("@aws-sdk/client-s3", () => ({
PutObjectCommand: jest.fn(),
S3Client: jest.fn(() => ({
send: jest.fn(() => "send"),
})),
}));
describe("upload product", () => {
it("should send status of 502 on failling on saving img", async () => {
await uploadProductHandler(mockReq, mockRes);
const setfail = jest.spyOn(S3Client, "send").mockImplementationOnce(() => Promise.reject("not saved"));
expect(generateRandom).toHaveBeenCalledTimes(3);
expect(generateRandom).toHaveBeenLastCalledWith(32);
expect(PutObjectCommand).toHaveBeenCalledTimes(2);
});
});
I did tried using "S3Client.prototype" insted of S3Client in jest.spyOn it gave same error
My code is giving error of "Property send
does not exist in the provided object"
Building up on @aayla-secura's response, if you want to test both send
call results (the one that resolves and the one that rejects), what you could do is declare the send mock as a separate const
The idea of using an arrow function comes from this other contribution: https://stackoverflow.com/a/68073694/12194386
const mockSend = jest.fn();
jest.mock("@aws-sdk/client-s3", () => ({
PutObjectCommand: jest.fn(),
S3Client: jest.fn(() => ({
send: () => mockSend(),
})),
}));
Another solution found in the Jest documentation (see https://jestjs.io/docs/es6-class-mocks) could be to use mockImplementation
:
const mockSend = jest.fn();
jest.mock("@aws-sdk/client-s3", () => ({
PutObjectCommand: jest.fn(),
S3Client: jest.fn().mockImplementation(() => ({
send: mockSend,
}));
}));
That way, you have the possibility to mock different behaviors
describe("upload product", () => {
it("should send status 502 if saving image fails", async () => {
mockSend.mockRejectedValueOnce(new Error('Some error'));
...
await uploadProductHandler(mockReq, mockRes);
...
expect(mockRes.sendStatus).toHaveBeenCalledWith(502);
});
it("should send status 201 when everything works correctly", async () => {
mockSend.mockResolvedValueOnce(null);
...
await uploadProductHandler(mockReq, mockRes);
...
expect(mockRes.sendStatus).toHaveBeenCalledWith(201);
});
});