使用 TypeORM 和 Nestjs 进行测试的过程,以及使用 mocks 的玩笑?

Posted

技术标签:

【中文标题】使用 TypeORM 和 Nestjs 进行测试的过程,以及使用 mocks 的玩笑?【英文标题】:Process of testing with TypeORM and Nestjs, and jest using mocks? 【发布时间】:2019-03-18 18:13:36 【问题描述】:

这个问题很可能被概括为服务中的存根存储库以及如何在这个问题的上下文中正确测试和提供覆盖范围。

我正在学习有关测试的更多信息,但我不知道如何正确执行涉及数据库的测试。

我有一个定义列和一些初始验证逻辑的用户实体。

    import  IsAlphanumeric, IsEmail, MinLength  from 'class-validator';
    import  Column, Entity, PrimaryGeneratedColumn  from 'typeorm';
    @Entity()
    export class User 
      @PrimaryGeneratedColumn()
      public id!: number;

      @Column()
      public name!: string;

      @IsEmail()
      @Column()
      public email!: string;

      @MinLength(8)
      @Column()
      public password!: string;
    

我有一个为实体注入存储库的 UserService。

    import  Injectable  from '@nestjs/common';
    import  InjectRepository  from '@nestjs/typeorm';
    import  validateOrReject  from 'class-validator';
    import  Repository  from 'typeorm';
    import  CreateUserDTO  from './dto/create-user.dto';
    import  User  from './user.entity';

    @Injectable()
    export class UserService 
      constructor(
        @InjectRepository(User) private readonly userRepository: Repository<User>
      ) 

      public async create(dto: CreateUserDTO) 
        const user = this.userRepository.create(dto);
        await validateOrReject(user);
        await this.userRepository.save(user);
      

      public async findAll(): Promise<User[]> 
        return await this.userRepository.find();
      

      public async findByEmail(email: string): Promise<User | undefined> 
        return await this.userRepository.findOne(
          where: 
            email,
          ,
        );
      
    

这是我的初步测试,所以你可以按照我的思路...

    import  Test, TestingModule  from '@nestjs/testing';
    import  getRepositoryToken  from '@nestjs/typeorm';
    import  User  from './user.entity';
    import  UserService  from './user.service';

    const createMock = jest.fn((dto: any) => 
      return dto;
    );

    const saveMock = jest.fn((dto: any) => 
      return dto;
    );

    const MockRepository = jest.fn().mockImplementation(() => 
      return 
        create: createMock,
        save: saveMock,
      ;
    );
    const mockRepository = new MockRepository();

    describe('UserService', () => 
      let service: UserService;

      beforeAll(async () => 
        const module: TestingModule = await Test.createTestingModule(
          providers: [
            UserService,
            
              provide: getRepositoryToken(User),
              useValue: mockRepository,
            ,
          ],
        ).compile();
        service = module.get<UserService>(UserService);
      );

      it('should be defined', () => 
        expect(service).toBeDefined();
      );

      it('should not create invalid user', async () => 
        // ??
      );
    );

因此,虽然我可以进行测试和所有操作,但我不确定我实际上应该测试什么。我显然可以测试它是否在创建时验证,对于 findAll 等其他东西,我觉得我只是在模拟数据库?为了让我正确测试它,是否需要将它连接到数据库以便我可以检查是否返回了正确的数据?

nest 文档说“我们通常希望避免任何数据库连接”,但这样做不会违背目的,因为我们没有真正测试功能吗?因为虽然我可以模拟保存返回一个值,但我没有测试唯一列、可空数据、要设置的递增值等可能发生的任何错误......对吗?

【问题讨论】:

“我不确定我实际上应该测试什么”,确切地说,因为您在服务中基本上没有什么要测试的。您唯一可以测试的是您的服务是否正确调用了您希望它调用的存储库方法并使用正确的参数,为此您将使用“spyOn”或“toHaveBeenCalledWith”。 TypeORM 已经是一个经过测试的库,所以你不想测试 TypeORM。针对数据库的测试是功能测试,而不是单元测试,在这种情况下,我将以端到端的方式重新组织您的测试,调用您的 API 端点并在调用后测试数据库。 【参考方案1】:

许多人认为对数据库进行测试是不好的做法。但是正是由于您提到的原因 + 为自己省去了管理模拟和存根的麻烦,我几乎总是针对专用的测试数据库运行我的测试。

在我开玩笑的启动中,我清除了所有表,然后让助手帮助我根据需要创建具有关系的实体,以确保我的测试保持原子性。

【讨论】:

好的,谢谢您的反馈。我最终做了一些非常相似的事情。我决定将 sqljs 用于测试数据库,并设置 TypeORM 以在每次测试后删除数据库并在每次测试开始时进行同步。它仍然非常快,就像你说的那样大大简化了事情。谢谢! 一般来说,对数据库进行测试没有什么不好,只要它不是单元测试的一部分。而且实际上不可能在不访问数据库的情况下进行 e2e 测试。因此,一切都取决于用例,唯一重要的部分是不要将所有测试混在一起。 @AyKarsi 如何将真实数据库存储库导入我的测试模块? 我觉得用 SQLite 之类的一次性数据库测试是可以的:memory:,demo:***.com/a/59483875/5172890 @kenberkeley 您的开发数据库应该与您的生产数据库匹配【参考方案2】:

@AyKarsi 的建议总比没有好,但这仍然是一种不好的做法。

单元测试应该模拟数据库和第三方 API 调用。

集成测试应该测试用真实数据库模拟的内容,并且只测试那部分。

端到端测试是为了检查整个应用程序是否完全连接。

更多详情可以阅读:https://martinfowler.com/articles/practical-test-pyramid.html

【讨论】:

以上是关于使用 TypeORM 和 Nestjs 进行测试的过程,以及使用 mocks 的玩笑?的主要内容,如果未能解决你的问题,请参考以下文章

什么是 nestjs typeorm 中的 getRepositoryToken 以及何时使用它?

nestjs使用Typeorm实现数据库CRUD操作

如何使用 NestJS 和 TypeORM 转换输入数据

如何在 NestJs 中捕获 Typeorm 事务错误

NestJS TypeORM InjectRepository 无法读取未定义的属性“原型”

NestJS TypeORM mongodb 在数组列中查找不起作用