使用NestJS测试猫鼬模型

时间:2019-03-13 13:48:28

标签: nestjs

我正在使用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

所示

4 个答案:

答案 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构造函数的语法,可以使用相同的语法编写我们的模拟。

TL; DR

模拟应该是带有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);
  });
})