Reputation: 48269
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 })
}
}
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('[email protected]')
})
})
})
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
.
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())
}
}
Upvotes: 5
Views: 10919
Reputation: 4506
Although your solution works, it causes repetition of db initialization code in all the repositories. I'd like to propose an alternate solution that works for me.
Step 1. Create a global configuration file jest.config.ts
in your project root folder.
// project-root/jest.config.ts
import type { Config } from '@jest/types';
const config: Config.InitialOptions = {
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: 'src',
testRegex: '.*\\.spec\\.ts$',
testEnvironment: 'node',
preset: 'ts-jest',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'], // [!code focus]
};
export default config;
Step 2. create global setup file src/jest.setup.ts
that contains global initialization code.
// src/jest.setup.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export const testDbConfig: TypeOrmModuleOptions = {
type: 'sqlite',
database: ':memory:',
dropSchema: true,
synchronize: true,
autoLoadEntities: true,
};
// assign the testDbConfig to a global variable
global.testDbConfig = testDbConfig;
Step 3: Create your repositories and inject the entity manager into the constructor.
import { EntityManager, Repository } from 'typeorm';
import { User } from './entities/user.entity';
export class UserRepository extends Repository<User> {
constructor(private readonly entityManager: EntityManager) {
super(Movie, entityManager);
}
// my custom method that I need to unit test
async findByEmail(title: string): Promise<User | undefined> {
return this.findOne({ where: { email } });
}
}
Step 4. Unit test your repositories by making entries into the in-memory database via the entityManager
and then try reading the entries with your custom repository methods.
describe('UsersRepository', () => {
let usersRepository: UsersRepository;
let entityManager: EntityManager;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [TypeOrmModule.forRoot(global.testDbConfig), TypeOrmModule.forFeature([User])],
providers: [
{
provide: UsersRepository,
useFactory: (entityManager: EntityManager) => new UsersRepository(entityManager),
inject: [EntityManager],
},
],
}).compile();
const repositoryToken = getRepositoryToken(UsersRepository);
usersRepository = module.get<UsersRepository>(repositoryToken);
entityManager = module.get<EntityManager>(EntityManager);
});
// clear the user table to avoid clash with other test cases
beforeEach(async () => {
await entityManager.clear(User);
});
it('Should find the user by his email-id correctly', async () => {
const testUser = new User();
testUser.email = "[email protected]"
await entityManager.save(User, testUser);
const fetchedUser = await usersRepository.findByEmail(testUser.email);
expect(fetchedUser).toBeDefined();
expect(fetchedUser.email).toEqual(testUser.email)
});
})
Once complete, just run the test suite. That's all!
Upvotes: 0
Reputation: 48269
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)
})
})
})
Upvotes: 8