我正在使用NestJS的mongoose模块,所以我有自己的架构和接口,在我的服务中,我使用@InjectModel注入我的模型。我不知道如何模拟要注入服务的模型。
我的服务如下:
@Injectable()
export class AuthenticationService {
constructor(@InjectModel('User') private readonly userModel: Model<User>) {}
async createUser(dto: CreateUserDto): Promise<User> {
const model = new this.userModel(dto);
model.activationToken = this.buildActivationToken();
return await model.save();
}
}
在我的服务测试中,我有这个:
const mockMongooseTokens = [
{
provide: getModelToken('User'),
useValue: {},
},
];
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
...mockMongooseTokens,
AuthenticationService,
],
}).compile();
service = module.get<AuthenticationService>(AuthenticationService);
});
但是当我运行测试时,我得到了这个错误:
TypeError: this.userModel is not a constructor
我也想让我的模型对它执行单元测试,如article
所示答案 0 :(得分:6)
我知道这篇文章较旧,但如果将来有人应该再次回答这个问题,这里有一个示例,说明如何设置模拟模型并监视任何底层查询调用方法。我花了比我想要的更长的时间来解决这个问题,但这里有一个完整的示例测试,不需要任何额外的工厂函数或任何东西。
import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/mongoose';
import { Model } from 'mongoose';
// User is my class and UserDocument is my typescript type
// ie. export type UserDocument = User & Document; <-- Mongoose Type
import { User, UserDocument } from './models/user.model';
import { UsersRepository } from './users.repository';
import * as CustomScalars from '@common/graphql/scalars/data.scalar';
describe('UsersRepository', () => {
let mockUserModel: Model<UserDocument>;
let mockRepository: UsersRepository;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: getModelToken(User.name),
useValue: Model // <-- Use the Model Class from Mongoose
},
UsersRepository,
...Object.values(CustomScalars),
],
}).compile();
// Make sure to use the correct Document Type for the 'module.get' func
mockUserModel = module.get<Model<UserDocument>>(getModelToken(User.name));
mockRepository = module.get<UsersRepository>(UsersRepository);
});
it('should be defined', () => {
expect(mockRepository).toBeDefined();
});
it('should return a user doc', async () => {
// arrange
const user = new User();
const userID = '12345';
const spy = jest
.spyOn(mockUserModel, 'findById') // <- spy on what you want
.mockResolvedValue(user as UserDocument); // <- Set your resolved value
// act
await mockRepository.findOneById(userID);
// assert
expect(spy).toBeCalled();
});
});
答案 1 :(得分:1)
为响应@jbh解决方案,解决在调用findById()
之类的方法时不实例化类的问题的一种方法是使用静态方法,您可以像这样使用
class mockModel {
constructor(public data?: any) {}
save() {
return this.data;
}
static findOne({ _id }) {
return data;
}
}
mockModel.findOne();
有关静态方法的更多信息:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static
答案 2 :(得分:0)
您的问题似乎并未引起太多关注,因为我遇到了相同的问题,因此这是我实现的解决方案。希望它对其他NestJS爱好者有帮助!
您得到的错误消息非常明确:this.userModel
确实不是构造函数,因为您为useValue
提供了一个空对象。为了确保有效注入,useValue
必须是mongoose.Model
的子类。猫鼬github repo本身对基本概念给出了一致的解释(来自第63行):
* In Mongoose, the term "Model" refers to subclasses of the `mongoose.Model`
* class. You should not use the `mongoose.Model` class directly. The
* [`mongoose.model()`](./api.html#mongoose_Mongoose-model) and
* [`connection.model()`](./api.html#connection_Connection-model) functions
* create subclasses of `mongoose.Model` as shown below.
换句话说,猫鼬模型是具有尝试连接数据库的几种方法的类。在我们的例子中,唯一使用的Model方法是save()
。猫鼬使用javascript构造函数的语法,可以使用相同的语法编写我们的模拟。
模拟应该是带有save()
参数的构造函数。
服务测试如下:
beforeEach(async () => {
function mockUserModel(dto: any) {
this.data = dto;
this.save = () => {
return this.data;
};
}
const module = await Test.createTestingModule({
providers: [
AuthenticationService,
{
provide: getModelToken('User'),
useValue: mockUserModel,
},
],
}).compile();
authenticationService = module.get<AuthenticationService>(AuthenticationService);
});
我还做了一些重构,将所有内容包装在beforeEach
块中。
我为测试选择的save()
实现是一个简单的标识函数,但是您可以根据要对createUser()
的返回值进行断言的方式来不同地实现它。
此解决方案的一个问题恰好是您在函数的返回值上断言,但不能在调用次数上断言,因为save()
不是jest.fn()
。我找不到使用module.get
来访问模块范围之外的模型令牌的方法。如果有人找到了解决方法,请告诉我。
另一个问题是必须在测试的类中创建userModel
的实例。例如,当您要测试findById()
时,这是有问题的,因为没有实例化模型,但是在集合上调用了该方法。解决方法是在new
级别添加useValue
关键字:
const module = await Test.createTestingModule({
providers: [
AuthenticationService,
{
provide: getModelToken('User'),
useValue: new mockUserModel(),
},
],
}).compile();
不应该使用return await
语法,因为它会引起ts-lint错误(规则:no-return-await)。请参阅相关的github doc issue。
答案 3 :(得分:0)
add new before each:
describe('Create mockmodel', () => {
beforeEach(async () => {
function mockUserModel(dto: any) {
this.data = dto;
this.save = () => {
return this.data;
};
}
const module = await Test.createTestingModule({
providers: [
AuthenticationService,
{
provide: getModelToken('User'),
useValue: mockUserModel,
},
],
}).compile();
authenticationService = module.get<AuthenticationService>(AuthenticationService);
});
})