skip to Main Content

I would like to mock this async function with Jest in a Typescript Express env.

// src/helpers/user.ts
import { UserModel } from "../models";

export const getUserByUsername = async (username: string) => {
  return UserModel.findOne({ username }).exec()
}
// src/models/index.ts
import { getModelForClass } from "@typegoose/typegoose";

export const UserModel = getModelForClass(User, { schemaOptions: { timestamps: true } });

Here is my the test that fails:

// src/helpers/user.test.ts
describe("getUserByUsername():", () => {
  test('should return a user if username matches', async () => {
    const mockUser: User = await userFactory.build("user");

    const spy = jest.spyOn(UserModel, "findOne");
    spy.mockImplementationOnce(() => Promise.resolve(mockUser))

    const userByUsernameResult = usersHelper.getUserByUsername(mockUser.username);

    await expect(userByUsernameResult).resolves.toEqual(expect.objectContaining({ username: mockUser.username })); 
  });
});

The error message I get:

expect(received).resolves.toEqual() Received promise rejected instead
of resolved Rejected to value: [TypeError:
models_1.UserModel.findOne(…).exec is not a function]

Could you please help me?

UPDATE: I use in some other tests the MongoDB Memory Server approach that works in this case too (see below). However, I would really like to know how to do it by mocking the .exec() (see above).

// src/helpers/user.test.ts
beforeAll(async () => {
  await mongoose.connect(process.env.MONGO_URL as string, { useNewUrlParser: true, useUnifiedTopology: true } as ConnectOptions, (err) => {
    if(err) {
      console.error(err);
      process.exit(1);
    }
  })
});

afterAll(async () => {
  await mongoose.connection.close();
});

describe("getUserByUsername():", () => {
  test('should return a user if username matches', async () => {
    const mockUser: User = await userFactory.build("user", {}, { setId: false });

    const validUser = new UserModel(mockUser);
    await validUser.save();

    const userByUsernameResult = usersHelper.getUserByUsername(mockUser.username);

    await expect(userByUsernameResult).resolves.toEqual(expect.objectContaining({ username: mockUser.username }));
  });

2

Answers


  1. I think this approach should work.

       const findOneMock = jest.spyOn(UserModel, 'findOne');
        findOneMock.mockResolvedValueOnce({ exec: jest.fn().mockResolvedValue(mockUser) } as any)
    

    in case of type error explicit type assertions on mock object approach.

        findOneMock.mockResolvedValueOnce({
          exec: jest.fn().mockResolvedValue(mockUser) as unknown as () => Promise<User>,
        } as any);
    
    Login or Signup to reply.
  2. Your case seems quite similar with this question.

    In similarity to the answer given there, I think that something like this could possibly work:

    UserModel.findOne = jest.fn().mockImplementationOnce(() => ({ exec: jest.fn().mockResolvedValueOnce(() => {/* Your Code Here.*/})}));
    

    To elaborate: exec() is a method chained with the call on UserModel.findOne. Therefore, you have to mock on two levels: one that defines a mock implementation for UserModel.findOne, and one that mocks the exec() as a chain call with UserModel.findOne.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search