javascriptjestjs

How to mock method on an Instance using Jest.js?


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"


Solution

  • 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);
      });
    });