레포지토리 테스트 코드를 작성할때 트랜잭션을 이용하면 디비에 값을 안쌓인 채로 테스팅을 지속적으로 할 수가있다.
고스락 티켓 예매 프로젝트에서는
위와 같은 방식으로 typeorm 으로 부터 받은 Repository<entitiy> 를 이용해서 커스텀 레포지토리를 만들어서 사용중이다.
하지만 우리가 만든 커스텀 레포지토리를 한번 주입받으면 커넥션이 물려있어서 트랜잭션이 다루기가 쉽지않다.
앞써 작성했던 글에서 queryRunner에서 repository를 얻어오는 방식으로 각각의 테스트가 실행되고 끝날때에 롤백을 해줄수 있는 틀을 잡을려고한다.
이글에서 얻어갈 수 있는 것들
- beforeAll 에서 커넥션이된 queryRunner 로 레포지토리 얻어오기
- beforeEach 에서 queryRunner 트랜잭션 시작하기
- afterEach , afterAll 로 커넥션 잘 정리해주기.
데이타 베이스 관련 모듈 설정
로컬 디비 테스트이므로 매번 TypeOrm 설정 소스 하기엔 조금 귀찮은것 같아서
커스텀 데이타 베이스 모듈을 따로 만들었었다.
@Module({})
export class DatabaseModule {
static forRoot(options: DatabaseOption): DynamicModule {
if (options.isTest) {
return {
module: DatabaseModule,
imports: [
TypeOrmModule.forRoot({
... /// 생략
})
]
};
}
return {
module: DatabaseModule,
imports: [
TypeOrmModule.forRootAsync({
... // 생략
})
})
]
};
}
}
src/database/database.module.ts
간단하게 forRoot 다이나믹 모듈로 isTest를 인자로 받아 테스트환경에서는 테스트스럽게 동작하도록 구성했다.
이렇게 만들어두면 테스트 코드 작성할때 저모듈만 임포트 받으면 typeorm이 datasource 를 프로바이딩 해준다.
소스 길어지는거 싫어서 함빼보았다..
describe
describe('userRepository Test ( Actual db )', () => {
let userRepository: UserRepository;
let dataBaseModule: TestingModule;
let repositoryModule: TestingModule;
let dataSource: DataSource;
let queryRunner: QueryRunner;
let userRepositoryFromDataSource: Repository<User>;
테스트 설명적는 부분이다.
userRepository 가 우리가 실제로 테스트할 레포지토리 이고,
queryRunner 로 beforEach , afterEach 등에서
트랜잭션 시작과 롤백 트랜잭션을 기술할 예정이다.
userRepositoryFromDataSource는 맨위 사진에서 typeorm의 레포지토리 형식이다.
userRepositoryFromDataSource를 통해서 userRepository를 얻어올 것이다.
beforAll
beforeAll(async () => {
dataBaseModule = await Test.createTestingModule({
imports: [
DatabaseModule.forRoot({ isTest: true }),
TypeOrmModule.forFeature([User])
]
// providers: [UserRepository]
}).compile();
dataSource = dataBaseModule.get<DataSource>(getDataSourceToken());
queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
userRepositoryFromDataSource = queryRunner.manager.getRepository(User);
repositoryModule = await Test.createTestingModule({
providers: [
UserRepository,
{
provide: UserRepository,
useValue: new UserRepository(userRepositoryFromDataSource)
}
]
}).compile();
userRepository = repositoryModule.get<UserRepository>(UserRepository);
});
각 테스트가 시작하기전에 한번 공통으로 실행되는 부분이다.
우리가 커스텀으로 만들었던 DatabaseModule을 땡겨오고 , 테스트 라고 명시해준다.
그러면서 사용할 User 엔티티를 적어주었다.
이렇게 만들어진 테스트 모듈에서 typeorm이 글로벌하게 뿌려주는 dataSource를 토큰으로 슬쩍 빼온뒤에
쿼리러너를 통해 디비와 커넥션을 만든다.
그뒤에 쿼리러너를 통해 User 엔티티로 리포지토리를 빼온다
이전에 작성했던 글은 이시점에 트랜잭션을 시작한 쿼리러너로 레포지토리를 빼왔지만 이번엔 커넥션만 연결한체로 빼온다!!!
그뒤에 테스팅모듈 활용해서 만들어진 userRepository 를 빼온다.
이건뭐.. 혹시나 레포지토리단에서 다른 provide 된 값들이 필요할까봐 이렇게 작성했고
userRepository = new UserRepository(userRepositoryFromDataSource)
위와 같은 형태로 기술해도 상관없다.
이렇게되면 전체 테스트 돌릴때 각 테스트 할때마다 굳이 커넥션을 계속 맺을 필요없이 테스트 시작전에 한번에 공통으로 커넥션을 맺은 상태에서 작업할 수 있다.
beforeEach
beforeEach(async () => {
await queryRunner.startTransaction();
});
위 discribe 범위로 설정했던 queryRunner에서 트랜잭션을 시작해준다.
이렇게 쿼리러너로 레포지토리를 따오면 해당 커넥션이 레포지토리에 유지가 되기때문에 트랜잭션을 껏다 켰다 해줄수가 있다.!
afterEach , afterAll
afterEach(async () => {
await queryRunner.rollbackTransaction();
});
afterAll(async () => {
await queryRunner.release();
await dataBaseModule.close();
await repositoryModule.close();
});
각 테스트가 끝난후 롤백이랑 , 리소스 반납해준다.
실제 테스트
it(' test', async () => {
const user = new User();
user.name = 'asdfasdfasc222';
user.phoneNumber = '123123123213123';
await userRepository.saveUser(user);
const users = await userRepository.findAll();
return;
});
});
요렇게 아주 깔끔한 코드가 나오고, 롤백 테스트이기때문에 디비에 영향도 안가는 아주 굳굳인 레포지토리 테스트를 짤 수가있다 ㅎㅎ
이렇게 틀한번 잡고나니 , 다음번 프로젝트할 때는 테스트할 용기가 생겼다.
ㅋㅋㅋㅋ 문제는 틀만 잡아버렸다... 항상 테스트... 해봐야지 해봐야지하면서,,,
핑계대면서 할려고안한다...
아무튼 이글에서 중요한 부분은
커넥션을 만들고 넘긴 쿼리러너로 만든 리포지토리가 쿼리러너의 동작상태가 나중에 바뀌어도 레포지토리한테
적용이된다는점이다.
어찌보면 당연한거다 요청별로 커넥션 만들어져야하니깐
사실근데 틀잡고... 테스트코드를 안짰다.. 요번에 codev 까지 해봤었는데 ㅋㅋ 퓨ㅠㅠㅠ
위 레포에서 전체 소스 확인 가능하다!
'nest.js' 카테고리의 다른 글
[고스락 티켓 2.0] nestjs swagger 같은 코드 여러 응답 예시 만들기 (2) - 성공응답 데코레이터 만들기 (1) | 2022.08.15 |
---|---|
[고스락 티켓 2.0] nestjs swagger 같은 코드 여러 응답 예시 만들기 (1) - @ApiProperty로 객체 만들기 (0) | 2022.08.14 |
[고스락 티켓 2.0] nestjs transaction with repository (0) | 2022.08.14 |
[고스락 티켓 2.0] nestjs redis forRootAsync 모듈 만들기 (0) | 2022.08.13 |
[고스락 티켓 2.0] nest js ValidationError custom (1) | 2022.08.13 |