https://github.com/viniciusjssouza/typeorm-transactional-tests
beforeEach
에서 연결된 커넥션 객체를 기준으로 transaction 생성 및 시작afterEach
에서 트랜잭션을 롤백시키고 커넥션을 반환beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [TypeOrmModule.forRoot(typeORMTestConfig)],
providers: [...],
}).compile();
dataSource = moduleFixture.get<DataSource>(DataSource);
});
beforeEach(async () => {
transactionalContext = new TransactionalTestContext(dataSource);
await transactionalContext.start();
});
afterEach(async () => {
await transactionalContext.finish();
});
Monkey patching
: 다른 사람이 작성한 코드(일반적으로 라이브러리나 프레임워크)를 변경하거나 확장하는 프로그래밍 기법
this.queryRunner = this.buildWrappedQueryRunner();
queryRunner
를 만들고 wrap
함수의 인자로 전달하여 실제 사용할 queryRunner
를 반환this.monkeyPatchQueryRunnerCreation(this.queryRunner);
createQueryRunner
함수가 originQueryRunnerFunction
에 저장DataSource
의 createQueryRunner
메소드를 queryRunner
를 반환하는 함수로 대체→ DataSource에서 createQueryRunner를 호출했을 때 매개변수로 받은 queryRunner가 반환된다!
이렇게 만들어진 queryRunner를 통해 트랜잭션을 시작!
constructor(private readonly connection: DataSource) {}
async start(): Promise<void> {
if (this.queryRunner) {
throw new Error('Context already started');
}
try {
**this.queryRunner = this.buildWrappedQueryRunner();
this.monkeyPatchQueryRunnerCreation(this.queryRunner);**
await this.queryRunner.connect();
await this.queryRunner.startTransaction();
} catch (error) {
await this.cleanUpResources();
throw error;
}
}
private **buildWrappedQueryRunner()**: QueryRunnerWrapper {
const queryRunner = this.connection.createQueryRunner();
return **wrap**(queryRunner);
}
private **monkeyPatchQueryRunnerCreation(queryRunner: QueryRunnerWrapper)**: void {
this.originQueryRunnerFunction = DataSource.prototype.createQueryRunner;
DataSource.prototype.createQueryRunner = () => queryRunner;
}
wrap
함수도 monkeyPatchQueryRunnerCreation
함수처럼 release 함수를 가로채어, 원래의 release 함수가 호출되지 않도록 한다.releaseQueryRunner
함수: 원래의 release 메소드를 복원하고 호출하는 역할const wrap = (originalQueryRunner: QueryRunner): QueryRunnerWrapper => {
release = originalQueryRunner.release;
originalQueryRunner.release = () => {
return Promise.resolve();
};
(originalQueryRunner as QueryRunnerWrapper).releaseQueryRunner = () => {
originalQueryRunner.release = release;
return originalQueryRunner.release();
};
return originalQueryRunner as QueryRunnerWrapper;
};