Search code examples
reactjsnext.jsts-jest

Issues with Jest and service method being called


I have the code blow in typescript and using jest in order to do some api testing. I am able to successfully setup the jest mock for the candidate service, and I am able to verify that the mock methods specified below are hit. So I see for example "delete" in the console. However, I'm not able to find a way to verify that that method was successfully called. I have tried several different approaches. Including doing jest.mocked(new CandidateService()), which then lets me spyon the service method, but still fails when verifying using toHaveBeenCalled(). Can anyone point me in the right direction? I am trying to verify that the delete method is successfully called from the api route, and mocking everything else, but can't seem to get the expect to work as expected.

jest.mock("@services/candidate.service", () => {
  return jest.fn().mockImplementation(() => ({
    getCandidate: (id: number) => {
      return candidates.filter((c) => c.id === id);
    },
    deleteCandidate: async () => {
      console.log('delete')
      return await Promise.resolve();
    },
  }));
});


describe("DELETE /api/candidate/{id}", () => {
  it("delete candidate successfully", async () => {
    const mockedService = jest.mocked(CandidateService);
    const { req, res } = createMocks({
      method: "DELETE",
      query: { id: 1 },
    });

    await handler(req, res);

    expect(mockedService).toHaveBeenCalled();
    expect(res._getStatusCode()).toBe(200);
  });
});

Here is the candidate service

    import type { Candidate } from "@prisma/client";
import { prisma } from "../server/db/client";

export default class CandidateService {
  async getCandidates(): Promise<Candidate[] | []> {
    return await prisma.candidate.findMany();
  }

  async getCandidate(id: number): Promise<Candidate | null> {
    const candidate = await prisma.candidate.findFirst({
      where: { id },
      include: {
        Tags: {
          select: {
            Tag: {
              select: {
                name: true,
              },
            },
          },
        },
      },
    });

    return candidate;
  }

  async upsertCandidate(candidate: Candidate): Promise<Candidate> {
    return await prisma.candidate.upsert({
      where: { id: candidate.id || 0 },
      create: {
        ...candidate,
      },
      update: {
        ...candidate,
      },
    });
  }

  async deleteCandidate(id: number) {
    await prisma.candidate.delete({
      where: { id },
    });
  }
}

Solution

  • Perhaps this is not the most correct solution, but it is quite compact and works.

    The main idea is to get an object that we can use with spyOn. Therefore, we put everything in a public variable, but if we immediately declare it with the necessary functions, we will have an error. Therefore, we assign it a little later.

    I left comments in the code:

    // it`s template for passing the type check
    // and we'll access this variable later
    let mocked: {deleteUser:()=>{}}; 
    
    jest.mock("./user.service", () => {
      // it's replacement the `mocked` template by the mock implementation
      // now we get variable `mocked` for `spyOn`
      return jest.fn().mockImplementation(() => (mocked = {
        deleteUser: async () => {
          console.log('mock delete')
          return await Promise.resolve();
        },
      }));
    });
    
    describe("DELETE /api/user/{id}", () => {
      it("delete user successfully", async () => {
        // now we can use `spyOn` for any method of `mocked`
        const mockDel = jest.spyOn(mocked, 'deleteUser')
        const { req, res } = createMocks({
          method: "DELETE",
          query: { id: 1 },
        });
        // we run `deleteUser` inside handler
        await handler(req, res); 
        expect(mockDel).toHaveBeenCalled(); // passed
        expect(res._getStatusCode()).toBe(200);
      });