트랜잭션 롤백 시리즈
체크 예외가 발생하더라도 롤백이 발생하게끔 설정을 해두었다.
그런데, 매번 내가 체크 예외를 발생시켜서 직접 확인해야 한다는 불편함이 있다.
테스트에서 강제로 체크 예외를 터뜨리게 하고서, 롤백이 작동하는지 확인할 수는 없을까?
@MockBean
public AdServiceV1 adServiceV1;
@DisplayName("체크 예외 발생 시 롤백된다")
@Test
void test1() throws Exception {
CreateUserRequest request = new CreateUserRequest("dusgh1234", "1234");
authServiceV1.creatUser(request);
LocalDate now = LocalDate.now();
LoginRequest loginRequest = new LoginRequest("dusgh1234", "1234");
authServiceV1.login(loginRequest, now);
// 첫 번째 트랜잭션 커밋
TestTransaction.flagForCommit();
TestTransaction.end();
// 새로운 트랜잭션 시작
TestTransaction.start();
AdRewardRequest adRequest = new AdRewardRequest("광고1", 2);
doThrow(new IOException())
.when(adServiceV1)
.createAdViewLog(any(), any());
// 여기서 체크 예외가 발생하고 롤백되어야 함
assertThatThrownBy(() -> pointKeyFacadeV1.createPointKeyForAd("dusgh1234", adRequest, LocalDateTime.now()))
.isInstanceOf(CustomException.class);
// 명시적으로 롤백 플래그 설정
TestTransaction.flagForRollback();
TestTransaction.end();
// 새로운 트랜잭션에서 검증
TestTransaction.start();
// 포인트 키가 롤백되었는지 확인
AvailablePointKey availablePointKey2 = pointKeyFacadeV1.getAvailablePointKey("dusgh1234");
assertThat(availablePointKey2.availablePointKey()).isEqualTo(1L);
keyLogRepository.deleteAll();
pointKeyRepository.deleteAll();
userRepository.deleteAll();
}
우선 위 코드가 최종적인 완성본이다.
체크 예외가 터지도록 mock을 하려면, 체크 예외를 던져야 한다
이번에 mocking을 하면서 알게 된 것이다.
처음에 이 예외가 떠서 확인해보니
모킹을 한 메서드가 체크 예외를 던지게끔 설정이 안돼있어서 그랬다.
프로덕션 코드에 throw Exception을 하는 게 좀 꺼려졌다.
테스트를 위해서 코드를 바꾸게 되는 것이라서 그렇다.
이 때문에, 실제로 체크 예외가 발생할 수 있는 부분을 찾아보니
로그를 찍는 부분에서 이런 예외가 발생할 수 있을 것 같았다.
(나중에 실제로 IO작업을 할 수 있으니)
롤백이 됐으면 로그인에서 생긴 보상은 남아있고 광고 시청후의 보상은 사라져야 한다.
그런데 테스트를 해도 사라지지 않았다.
logging.level.org.springframework.transaction=TRACE
logging.level.org.springframework.orm.jpa=DEBUG
트랜잭션 관련 로그를 확인해보기로 했다.
내 예상과 달리 예외가 발생했을 때 바로 롤백이 되지 않았다. 여기선 마킹만 해둔다는 로그가 떴다.
실제 롤백이 시작되진 않은 상태에서
DB에서 데이터를 조회한다. 그러다보니 롤백이 반영되지 않은 것이다.
롤백은 제일 마지막에 실행됐다.
데이터 로직상 로그인 보상->(커밋) ->광고시청 보상->(롤백)이 돼야 해서 중간에 한번 커밋을 하고, 그 뒤에 롤백이 되게끔 했다.
로그인하고서 보상을 받는 부분은 커밋이 됐다.
이번에는 롤백이 마킹되고 바로 시작되는 것을 볼 수 있다.
생각보다 쉽지 않았지만 로그를 통해서 트랜잭션 상태를 보는 법을 새롭게 배워서 기분이 좋다.
이렇게 한번 커밋을 해주는 부분이 있다.
이 때문에 test용 db를 한번 다 지워주는 코드가 필요한데
이상하게 다시 테스트를 돌리면 DB에 USER가 이미 있다는 예외가 떴다.
DB 데이터가 제대로 안 지워진 것이다.
다시 로그를 봐보자.
delete를 하고 마지막에 다시 롤백을 한다(테스트 클래스에 달린 트랜잭션 때문에)
딜리트하는 것이 롤백이 되버리는 것으로 보인다.
@AfterEach
void cleanup() {
keyLogRepository.deleteAll();
pointKeyRepository.deleteAll();
userRewardActivityRepository.deleteAll();
userRepository.deleteAll();
TestTransaction.flagForCommit();
TestTransaction.end();
}
그래서 이렇게 여기서는 롤백이 되지 않도록 커밋을 했다.
이제 로그인 보상에서 예외가 발생할 때 롤백이 되는지 확인해보자.
이렇게 MockBean을 만들었다.
그런데, 갑자기 그전에 잘되던 테스트가 실패했다.
우리는 PointKey가 생성될 때마다 로그를 남기는데
이 로그가 만들어지지 않은 것이다.
그 전에 만든 테스트는 로그인 후(로그인 보상에 따른 포인트를 받는다)->광고 시청 도중 예외(보상을 받았지만 롤백)이라서, 로그인 보상에 따른 로그는 만들어져야 한다.
그런데 갑자기 로그인 보상에 대한 로그가 만들어지지 않는 것..!
디버깅을 해보자.
새로운 테스트를 위해서 PointKeyLogService를 MockBean으로 등록했더니
기존 테스트가 영향을 받은 것이다.
PointKeyLogService 이 안에 들어 있어야 할 의존성들이 null이 됐다. PointKeyLog를 저장할 Repository가 null이 돼서 로그를 저장하지 못한 것이다.
광고보상-로그인보상 둘다 log나 pointKey를 생성할 때 같은 클래스들에 의존하고 있어서
둘 중 한 곳에서 이 클래스를 mock으로 처리하면, 다른 테스트에 영향을 주게 되는 상황이다.
그래서 테스트 클래스를 두개로 분리했다.
AdPointRollback 클래스에서는 AdServiceV1를, LoginRollback 클래스에서는 PointKeyLogServiceV1를 Mock으로 했다.
테스트들이 서로 영햐을 주는 문제를 해결할 수 있었다.