【问题标题】:Inject TypeORM repository into NestJS service for mock data testing将 TypeORM 存储库注入 NestJS 服务以进行模拟数据测试
【发布时间】:2019-08-17 08:39:57
【问题描述】:

issue 中关于如何执行此操作的讨论很长。

我已经尝试了许多建议的解决方案,但运气不佳。

谁能提供一个具体的例子来说明如何使用注入的存储库和模拟数据来测试服务?

【问题讨论】:

    标签: javascript node.js typescript nestjs


    【解决方案1】:

    假设我们有一个非常简单的服务,它通过 id 查找用户实体:

    export class UserService {
      constructor(@InjectRepository(UserEntity) private userRepository: Repository<UserEntity>) {
      }
    
      async findUser(userId: string): Promise<UserEntity> {
        return this.userRepository.findOne(userId);
      }
    }
    

    然后您可以使用以下模拟工厂模拟UserRepository(根据需要添加更多方法):

    // @ts-ignore
    export const repositoryMockFactory: () => MockType<Repository<any>> = jest.fn(() => ({
      findOne: jest.fn(entity => entity),
      // ...
    }));
    

    使用工厂可确保每次测试都使用新的模拟。

    describe('UserService', () => {
      let service: UserService;
      let repositoryMock: MockType<Repository<UserEntity>>;
    
      beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
          providers: [
            UserService,
            // Provide your mock instead of the actual repository
            { provide: getRepositoryToken(UserEntity), useFactory: repositoryMockFactory },
          ],
        }).compile();
        service = module.get<UserService>(UserService);
        repositoryMock = module.get(getRepositoryToken(UserEntity));
      });
    
      it('should find a user', async () => {
        const user = {name: 'Alni', id: '123'};
        // Now you can control the return value of your mock's methods
        repositoryMock.findOne.mockReturnValue(user);
        expect(service.findUser(user.id)).toEqual(user);
        // And make assertions on how often and with what params your mock's methods are called
        expect(repositoryMock.findOne).toHaveBeenCalledWith(user.id);
      });
    });
    

    为了类型安全和舒适,您可以为您的(部分)模拟使用以下类型(远非完美,当 jest 本身在即将发布的主要版本中开始使用 typescript 时可能会有更好的解决方案):

    export type MockType<T> = {
      [P in keyof T]?: jest.Mock<{}>;
    };
    

    【讨论】:

    • 很好的答案。我不知道提供程序中的useFactory
    • 什么是 MockType?
    • @jackabe 见最后一段。这是一个类型定义,应该让使用 jest mocks 更舒服,但它有一些限制。
    • 就我而言,我需要在service.findUser(user.id)之前添加await
    • 我找到了解决方案并编辑了答案。
    【解决方案2】:

    我的解决方案使用 sqlite 内存数据库,我在其中插入所有需要的数据并在每次测试运行之前创建架构。所以每个测试都使用相同的数据集,您不必模拟任何 TypeORM 方法:

    import { Test, TestingModule } from "@nestjs/testing";
    import { CompanyInfo } from '../../src/company-info/company-info.entity';
    import { CompanyInfoService } from "../../src/company-info/company-info.service";
    import { Repository, createConnection, getConnection, getRepository } from "typeorm";
    import { getRepositoryToken } from "@nestjs/typeorm";
    
    describe('CompanyInfoService', () => {
      let service: CompanyInfoService;
      let repository: Repository<CompanyInfo>;
      let testingModule: TestingModule;
    
      const testConnectionName = 'testConnection';
    
      beforeEach(async () => {
        testingModule = await Test.createTestingModule({
          providers: [
            CompanyInfoService,
            {
              provide: getRepositoryToken(CompanyInfo),
              useClass: Repository,
            },
          ],
        }).compile();
    
        let connection = await createConnection({
            type: "sqlite",
            database: ":memory:",
            dropSchema: true,
            entities: [CompanyInfo],
            synchronize: true,
            logging: false,
            name: testConnectionName
        });    
    
        repository = getRepository(CompanyInfo, testConnectionName);
        service = new CompanyInfoService(repository);
    
        return connection;
      });
    
      afterEach(async () => {
        await getConnection(testConnectionName).close()
      });  
    
      it('should be defined', () => {
        expect(service).toBeDefined();
      });
    
      it('should return company info for findOne', async () => {
        // prepare data, insert them to be tested
        const companyInfoData: CompanyInfo = {
          id: 1,
        };
    
        await repository.insert(companyInfoData);
    
        // test data retrieval itself
        expect(await service.findOne()).toEqual(companyInfoData);
      });
    });
    

    我在这里得到了启发:https://gist.github.com/Ciantic/be6a8b8ca27ee15e2223f642b5e01549

    【讨论】:

    • 就像拥有一个测试数据库的方法。这可以进一步改进。
    【解决方案3】:

    我还发现这对我有用:

    export const mockRepository = jest.fn(() => ({
      metadata: {
        columns: [],
        relations: [],
      },
    }));
    

    const module: TestingModule = await Test.createTestingModule({
          providers: [{ provide: getRepositoryToken(Entity), useClass: mockRepository }],
        }).compile();
    

    【讨论】:

      【解决方案4】:

      您也可以使用测试数据库并在其中插入数据。

      describe('EmployeesService', () => {
        let employeesService: EmployeesService;
        let moduleRef: TestingModule;
      
        beforeEach(async () => {
          moduleRef = await Test.createTestingModule({
            imports: [
              TypeOrmModule.forFeature([Employee]),
              TypeOrmModule.forRoot({
                type: 'postgres',
                host: 'db',
                port: 5432,
                username: 'postgres',
                password: '',
                database: 'test',
                autoLoadEntities: true,
                synchronize: true,
              }),
            ],
            providers: [EmployeesService],
          }).compile();
      
          employeesService = moduleRef.get<EmployeesService>(EmployeesService);
        });
      
        afterEach(async () => {
          // Free DB connection for next test
          await moduleRef.close();
        });
      
        describe('findOne', () => {
          it('returns empty array', async () => {
            expect(await employeesService.findAll()).toStrictEqual([]);
          });
        });
      });
      

      您需要手动创建数据库,例如psql -U postgres -c 'create database test;'。架构同步将自动发生。

      【讨论】:

      • autoLoadEntities 对我不起作用,所以我使用了字符串路径。非常感谢这个简单的设置示例!也可以使用初始化迁移创建 test_db。
      【解决方案5】:

      从上述想法开始并帮助模拟任何类,我们提出了这个 MockFactory:

      export type MockType<T> = {
          [P in keyof T]?: jest.Mock<unknown>;
      };
      
      export class MockFactory {
          static getMock<T>(type: new (...args: any[]) => T, includes?: string[]): MockType<T> {
              const mock: MockType<T> = {};
      
              Object.getOwnPropertyNames(type.prototype)
                  .filter((key: string) => key !== 'constructor' && (!includes || includes.includes(key)))
                  .map((key: string) => {
                      mock[key] = jest.fn();
                  });
      
              return mock;
          }
      }
      
      const module: TestingModule = await Test.createTestingModule({
          providers: [
              {
                  provide: getRepositoryToken(MyCustomRepository),
                  useValue: MockFactory.getMock(MyCustomRepository)
              }
          ]
      }).compile();
      

      【讨论】:

      • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
      猜你喜欢
      • 2020-06-06
      • 2019-07-05
      • 2022-09-24
      • 2020-04-19
      • 2021-11-21
      • 2021-07-15
      • 1970-01-01
      • 1970-01-01
      • 2021-09-14
      相关资源
      最近更新 更多