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
I think this approach should work.
in case of type error explicit type assertions on mock object approach.
Your case seems quite similar with this question.
In similarity to the answer given there, I think that something like this could possibly work:
To elaborate:
exec()
is a method chained with the call onUserModel.findOne
. Therefore, you have to mock on two levels: one that defines a mock implementation forUserModel.findOne
, and one that mocks theexec()
as a chain call withUserModel.findOne
.