typescriptunit-testingjestjsnestjstypeorm

How to unit test a custom repository of TypeORM in NestJS?


Class to test

My TypeORM repository extends AbstractRepository:

@EntityRepository(User)
export class UsersRepository extends AbstractRepository<User> {

  async findByEmail(email: string): Promise<User> {
    return await this.repository.findOne({ email })
  }
}

Unit test

describe('UsersRepository', () => {
  let usersRepository: UsersRepository

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [UsersRepository]
    }).compile()

    usersRepository = module.get<UsersRepository>(UsersRepository)
  })

  describe('findByEmail', () => {
    it(`should return the user when the user exists in database.`, async () => {
      const fetchedUser = await usersRepository.findByEmail('test1@test.com')
    })
  })
})

Here, I get the error:

TypeError: Cannot read property 'getRepository' of undefined

      at UsersRepository.get (repository/AbstractRepository.ts:43:29)
      at UsersRepository.findByEmail (users/users.repository.ts:11:23)
      at Object.<anonymous> (users/users.repository.spec.ts:55:49)

So, my question is, how do I mock the repository or repository.findOne?

In other words, how do I mock the fields that are inherited from the AbstractRepository which are protected and cannot be accessed from UsersRepository instance?

There is a similar question here but it is for extending from Repository<Entity> instead of AbstractRepository<Entity>. They are able to mock findOne because it's public.


What I tried

I tried to mock it in a NestJS recommended way, but this is for non-custom repositories and doesn't work in my case:

{
  provide: getRepositoryToken(User),
  useValue: {
    findOne: jest.fn().mockResolvedValue(new User())
  }
}

Solution

  • I went with the in-memory database solution. This way I don't have to mock the complex queries of TypeORM. The unit tests run as fast without hitting the real database.

    My production database is PostgreSQL, but I'm able to use SQLite in-memory database for unit testing. This works because the TypeORM provides an abtraction over databases. It doesn't matter what database we are using under the hood as long as we are satisfying the interface of our repository.

    Here's how my tests look like:

    const testConnection = 'testConnection'
    
    describe('UsersRepository', () => {
      let usersRepository: UsersRepository
    
      beforeEach(async () => {
        const connection = await createConnection({
          type: 'sqlite',
          database: ':memory:',
          dropSchema: true,
          entities: [User],
          synchronize: true,
          logging: false,
          name: testConnection
        })
    
        usersRepository = connection.getCustomRepository(UsersRepository)
      })
    
      afterEach(async () => {
        await getConnection(testConnection).close()
      })
    
      describe('findByEmail', () => {
        it(`should return the user when the user exists in database.`, async () => {
          await usersRepository.createAndSave(testUser)
          const fetchedUser = await usersRepository.findByEmail(testUser.email)
          expect(fetchedUser.email).toEqual(testUser.email)
        })
      })
    })