将 TypeORM 存储库注入 NestJS 服务进行模拟数据测试

IT技术 javascript node.js typescript nestjs
2021-01-28 17:17:54

在这个issue 中有一个关于如何做到这一点的冗长讨论

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

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

4个回答

假设我们有一个非常简单的服务,它通过 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在提供者中。
2021-03-16 17:17:54
@jackabe 见最后一段。这是一个类型定义,应该使使用 jest mocks 更舒适,但它有一些限制。
2021-03-22 17:17:54
什么是 MockType?
2021-04-01 17:17:54
我找到了解决方案并编辑了答案。
2021-04-01 17:17:54
就我而言,我需要补充awaitservice.findUser(user.id)
2021-04-12 17:17:54

我的解决方案使用 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

就像拥有测试数据库的方法一样。这可以进一步改进。
2021-03-21 17:17:54

我还发现这对我有用:

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

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

您还可以使用测试数据库并在那里插入数据。

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对我不起作用,所以我使用了字符串路径。这个简单的设置示例非常感谢!也可以使用 init 迁移创建 test_db。
2021-04-05 17:17:54