You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS+TypeORM中repository.findOneOrFail不是函数的问题求助

NestJS + TypeORM 集成测试中 findOneOrFail 方法不存在的问题

业务背景

我正在用NestJS和TypeORM开发物业管理应用,核心逻辑:租户支付租金时创建RentPayment实体,触发Rent实体的创建或更新(Rent实体包含该租户的RentPayments列表),当支付总和等于总租金时生成RentPaymentReceipt。

现有代码实现

CreateRentService 代码

@Injectable()
export class CreateRentService {
  constructor(
    @InjectRepository(Rent)
    private rentRepository: Repository<Rent>,
    @InjectRepository(RentPayment)
    private rentPaymentRepository: Repository<RentPayment>,
    @InjectRepository(Tenant)
    private tenantRepository: Repository<Tenant>,
  ) {}

  async execute(rentPaymentId: number) {
    const rentPayment = await this.rentPaymentRepository.findOneOrFail({
      where: {
        id: rentPaymentId,
      },
    });
    const tenant = rentPayment.tenant;
    const rent = new Rent(
      tenant.userId,
      tenant,
      rentPayment.paymentDate.toISOString(),
      [rentPayment],
    );
    return this.rentRepository.save(rent);
  }
}

PropertyModule 配置

@Module({
  imports: [TypeOrmModule.forFeature([RentPayment, Tenant, Property, Rent])],
  providers: [
    RentPaymentService,
    CreateRentService,
  ]
});

测试报错信息

运行集成测试时触发以下错误:

● Create rent › should create a rent when it does not already exist

    TypeError: this.rentPaymentRepository.findOneOrFail is not a function

      18 |
      19 |   async execute(rentPaymentId: number) {
    > 20 |     const rentPayment = await this.rentPaymentRepository.findOneOrFail({
         |                                                          ^
      21 |       where: {
      22 |         id: rentPaymentId,
      23 |       },

测试代码

describe('Create rent', () => {
  let typeOrmRentPaymentRepository: Repository<RentPayment>;
  let typeOrmTenantRepository: Repository<Tenant>;
  let typeOrmPropertyRepository: Repository<Property>;
  let typeOrmRentRepository: Repository<Rent>;
  let createRentService: CreateRentService;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      imports: [
        TypeOrmModule.forRoot(postgresqlTestDatasource.options),
        TypeOrmModule.forFeature([RentPayment, Tenant, Property, Rent]),
      ],
      providers: [
        {
          provide: getRepositoryToken(RentPayment),
          useValue: Repository,
        },
        {
          provide: getRepositoryToken(Tenant),
          useValue: Repository,
        },
        {
          provide: getRepositoryToken(Property),
          useValue: Repository,
        },
        {
          provide: getRepositoryToken(Rent),
          useValue: Repository,
        },
        CreateRentService,
      ],
    }).compile();

    typeOrmRentPaymentRepository = module.get<Repository<RentPayment>>(
      getRepositoryToken(RentPayment),
    );
    typeOrmTenantRepository = module.get<Repository<Tenant>>(
      getRepositoryToken(Tenant),
    );
    typeOrmPropertyRepository = module.get<Repository<Property>>(
      getRepositoryToken(Property),
    );
    typeOrmRentRepository = module.get<Repository<Rent>>(
      getRepositoryToken(Rent),
    );

    createRentService = module.get<CreateRentService>(CreateRentService);
  });

  afterEach(async () => {
    await typeOrmRentRepository.clear();
    await typeOrmRentPaymentRepository.clear();
    await typeOrmTenantRepository.clear();
    await typeOrmPropertyRepository.clear();
  });

  it('should create a rent when it does not already exist', async () => {
    // Given
    const userId = 32;
    const property = await typeOrmPropertyRepository.save(new Property(userId));
    const tenant = await typeOrmTenantRepository.save(
      new Tenant(userId, property),
    );
    const rentPayment = await typeOrmRentPaymentRepository.save(
      new RentPayment(tenant, 170000, new Date(2023, 0, 2, 7)),
    );

    // When
    await createRentService.execute(rentPayment.id);

    // Then
    const expectedRent = new Rent(userId, tenant, '2023-01-01', [rentPayment]);
    const createdRents = await typeOrmRentRepository.find();
    expect(createdRents.length).toEqual(1);
    expect(createdRents[0].tenant).toEqual(expectedRent.tenant);
    expect(createdRents[0].date).toEqual(expectedRent.date);
    expect(createdRents[0].rentPayments.length).toEqual(1);
    expect(createdRents[0].rentPayments[0]).toEqual(rentPayment);
  });
});

问题分析与解决方案

原因

测试模块中手动配置仓库provider时,将useValue设为了Repository类本身,而非实例化的仓库对象或模拟对象。TypeORM的findOneOrFail等方法都是实例方法,直接传入类的话,调用时自然找不到对应的方法。

修复方案

集成测试场景(使用真实仓库)

移除测试模块中手动覆盖仓库provider的配置,因为TypeOrmModule.forFeature已经会自动提供对应的仓库实例:

beforeEach(async () => {
  const module = await Test.createTestingModule({
    imports: [
      TypeOrmModule.forRoot(postgresqlTestDatasource.options),
      TypeOrmModule.forFeature([RentPayment, Tenant, Property, Rent]),
    ],
    providers: [CreateRentService], // 仅保留服务,无需手动提供仓库
  }).compile();

  // 直接获取TypeOrm自动注入的仓库实例
  typeOrmRentPaymentRepository = module.get<Repository<RentPayment>>(
    getRepositoryToken(RentPayment),
  );
  typeOrmTenantRepository = module.get<Repository<Tenant>>(
    getRepositoryToken(Tenant),
  );
  typeOrmPropertyRepository = module.get<Repository<Property>>(
    getRepositoryToken(Property),
  );
  typeOrmRentRepository = module.get<Repository<Rent>>(
    getRepositoryToken(Rent),
  );

  createRentService = module.get<CreateRentService>(CreateRentService);
});

单元测试场景(模拟仓库)

如果是单元测试需要模拟仓库行为,需创建包含模拟方法的对象,而非直接传入Repository类:

{
  provide: getRepositoryToken(RentPayment),
  useValue: {
    findOneOrFail: jest.fn().mockResolvedValue(mockRentPayment),
    save: jest.fn().mockResolvedValue(mockRentPayment),
  },
}

内容的提问来源于stack exchange,提问作者gvann2

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 10:40:17