In this post we’ll have a quick look at mocking Mongoose models so that they can be used in unit testing.
Automock
TestBed
from @automock/jest
allows us to simply instantiate an injectable class, with all dependecies automatically mocked. Importantly, we can gain a reference to the mocked model by referencing to it via the getModelToken(Model)
function.
let model: jest.Mocked<Model<User>>;
let service: UserService;
beforeAll(() => {
const { unit, unitRef } = TestBed.create(UserService).compile();
service = unit;
model = unitRef.get(getModelToken(User.name));
});
This allows us to fake specific behaviour via model.mockResolvedValueOnce(...)
Typing
When we create a mock using TestBed
we get an object with the actual type of the original module. On which we can call mockResolvedValueOnce
. When we mock this method using the wrong type signatures, we get a nice error telling us we’ve incorrectly mocked the method.

Overloads
It wouldn’t be TypeScript without some issues. When we try to create a mock implementation for one of Mongooses methods, we often get a type error. This is due to the usage of method overloading. Most methods in Mongoose switch return type based on the given input. Take create
for example:
/** Mongoose create function overloads */
create<DocContents = AnyKeys<TRawDocType>>(docs: Array<TRawDocType | DocContents>, options: CreateOptions & { aggregateErrors: true }): Promise<(THydratedDocumentType | Error)[]>;
create<DocContents = AnyKeys<TRawDocType>>(docs: Array<TRawDocType | DocContents>, options?: CreateOptions): Promise<THydratedDocumentType[]>;
create<DocContents = AnyKeys<TRawDocType>>(doc: DocContents | TRawDocType): Promise<THydratedDocumentType>;
create<DocContents = AnyKeys<TRawDocType>>(...docs: Array<TRawDocType | DocContents>): Promise<THydratedDocumentType[]>;
We can circumvent this issue by creating a small utility type, forcing typescript to select the correct overload.
// Utility type for selecting the correct overload
type SingleCreate = jest.MockedFunction<
<TRaw, THyd>(doc: TRaw) => Promise<THyd>
>;
(model.create as SingleCreate).mockResolvedValueOnce(
plainToInstance(User, { name: 'Named' }),
);
Example
Putting it all together:
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from 'src/schema/user.schema';
@Injectable()
export class UserService {
constructor(
@InjectModel(User.name) private readonly userModel: Model<User>,
) {}
public async exists(name: string) {
return !!(await this.userModel.exists({ name }));
}
public createUser(name: string) {
return this.userModel.create({ name });
}
}
import { Model, Types } from 'mongoose';
import { UserService } from './user.service';
import { User } from 'src/schema/user.schema';
import { TestBed } from '@automock/jest';
import { getModelToken } from '@nestjs/mongoose';
import { plainToInstance } from 'class-transformer';
type SingleCreate = jest.MockedFunction<
<TRaw, THyd>(doc: TRaw) => Promise<THyd>
>;
describe('UserService Automock', () => {
let model: jest.Mocked<Model<User>>;
let service: UserService;
beforeAll(() => {
const { unit, unitRef } = TestBed.create(UserService).compile();
service = unit;
model = unitRef.get(getModelToken(User.name));
});
it('is true when exists', async () => {
model.exists.mockResolvedValueOnce({ _id: new Types.ObjectId() });
expect(service.exists('name')).resolves.toBe(true);
});
it('returns an user when created', async () => {
(model.create as SingleCreate).mockResolvedValueOnce(
plainToInstance(User, { name: 'Named' }),
);
expect(service.createUser('named')).resolves.toBeInstanceOf(User);
});
});