I want to write such a unit test file that each time I run it, I run an instance of the entire application. This is ensured by this part:
module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
Then I wanted to clean up the database after each unit test. ChatGPT led me to the conclusion that I should use transactions. It suggested that I should use:
beforeEach(async () => {
await dataSource.query('BEGIN');
});
afterEach(async () => {
await dataSource.query('ROLLBACK');
});
But it didn't want to work for me.
Then after some chatting with the chat, I came to the conclusion that I should use queryRunner.startTransaction()
and queryRunner.rollbackTransaction()
. But it still doesn't want to work for me.
Please help me fix the below code.
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { AppModule } from '../../app.module';
import { DataSource, QueryRunner } from 'typeorm';
import { DATA_SOURCE } from '../database/database.providers';
import { runSeeders } from 'typeorm-extension';
import { ConfigService } from '@nestjs/config';
describe('UsersService', () => {
let service: UsersService;
let module: TestingModule;
let dataSource: DataSource;
let queryRunner: QueryRunner;
let config: ConfigService;
let createQueryRunner;
let release;
beforeAll(async () => {
module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
config = module.get<ConfigService>(ConfigService);
dataSource = module.get<DataSource>(DATA_SOURCE);
if (config.get<string>('NODE_ENV') === 'testing') {
console.info('Running seeders for testing environment');
await runSeeders(dataSource);
}
service = module.get<UsersService>(UsersService);
});
beforeEach(async () => {
queryRunner = dataSource.createQueryRunner();
await queryRunner.startTransaction();
});
afterEach(async () => {
await queryRunner.rollbackTransaction();
await queryRunner.release();
});
afterAll(async () => {
await module.close();
});
it('should be defined', async () => {
await service.create({ firstName: 'FN', lastName: 'LN', email: 'dan@example.com' });
await service.create({ firstName: 'FN2', lastName: 'LN2', email: 'dan2@example.com' });
expect(service).toBeDefined();
});
it('should return a user', async () => {
const user = await service.findOneByEmail('dan@example.com');
expect(user).toBeDefined();
});
});
Through some trial and error, I came to this solution. I used the <<==
comments to highlight lines that were added to the previous example.
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { AppModule } from '../../app.module';
import { DataSource, QueryRunner } from 'typeorm';
import { DATA_SOURCE } from '../database/database.providers';
import { runSeeders } from 'typeorm-extension';
import { ConfigService } from '@nestjs/config';
describe('UsersService', () => {
let service: UsersService;
let module: TestingModule;
let dataSource: DataSource;
let queryRunner: QueryRunner;
let config: ConfigService;
let createQueryRunner;
let release;
beforeAll(async () => {
module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
config = module.get<ConfigService>(ConfigService);
dataSource = module.get<DataSource>(DATA_SOURCE);
if (config.get<string>('NODE_ENV') === 'testing') {
console.info('Running seeders for testing environment');
await runSeeders(dataSource);
}
service = module.get<UsersService>(UsersService);
});
beforeEach(async () => {
queryRunner = dataSource.createQueryRunner();
await queryRunner.startTransaction();
createQueryRunner = dataSource.createQueryRunner; // <<==
release = queryRunner.release; // <<==
dataSource.createQueryRunner = () => queryRunner; // <<==
queryRunner.release = () => Promise.resolve(); // <<==
});
afterEach(async () => {
await queryRunner.rollbackTransaction();
dataSource.createQueryRunner = createQueryRunner; // <<==
queryRunner.release = release; // <<==
await queryRunner.release();
});
afterAll(async () => {
await module.close();
});
it('should be defined', async () => {
await service.create({ firstName: 'FN', lastName: 'LN', email: 'dan@example.com' });
await service.create({ firstName: 'FN2', lastName: 'LN2', email: 'dan2@example.com' });
expect(service).toBeDefined();
});
it('should return a user', async () => {
const user = await service.findOneByEmail('dan@example.com');
expect(user).toBeDefined();
});
});
Explanation:
I suppose that almost every time a query to the database is made we use dataSource.createQueryRunner().query(<SOME SQL QUERY>)
, which creates a new queryRunner. But for transactions to work for us in our tests file we must use the same queryRunner
each time a query is made. Above is shown how I figured out how to enforce this behavior. If anyone has a better solution, please feel free to share it. I hope that this problem in TypeORM will be fixed sometime soon, or some better workaround will be introduced.