Search code examples
nestjsrepositorytypeorm

nestJS / typeOrm repository findOneOrFail is not a function


I am building a nestJS app with typeOrm. It is a property management application. When a tenant pays a landlord, it creates a RentPayment entity, which triggers the creation or update of a Rent entity. This latter will have a list of RentPayments for a particular tenant. A RentPaymentReceipt will be generated when the sum of the rent payments matches the total rent.

I am currently developing the CreateRentService. I have injected the repositories I need :

@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);
  }

When I run the service via an integration test, I have a findOneOrFail is not a function error:

  ● 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 |       },

My property module imports all the typeOrm entities required:

property.module.ts:

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

I have called the same function(findOneOrFail) from within the test and there is no problem. Here is my test file:

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);
  });
});

I am expecting the function to be part of the repository, as written in typeOrm documentation, and as my ide (IntelliJ + webstorm plugin) is suggesting me.

It is the same behavior with the tenantRepository...

I have spent several hours reading typeOrm and nestJs documentation, as well as trying to find an answer on SO. The same type of problem I have seen were because the entity was not imported in the module via imports: [TypeOrmModule.forFeature([entity]) But I think I have done it as suggested and required by the documentation.

Many thanks in advance for your help on this.


Solution

  • @Jay's answer set me on the track to find my error. As I just wanted to obtain a reference to the "real" injected repository, I just had to get rid of the different repository providers I gave to my test config:

    beforeEach(async () => {
        const module = await Test.createTestingModule({
          imports: [
            TypeOrmModule.forRoot(postgresqlTestDatasource.options),
            TypeOrmModule.forFeature([RentPayment, Tenant, Property, Rent]),
          ],
          providers: [
            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);
      });
    

    The module.get<Repository<Entity>>(getRepositoryToken(Entity) lines are doing the job of getting the correct reference to the injected repositories