@Transactional 전파에 대해 공부하면서 테스트 코드를 작성하던 도중 이유를 알수 없는 오류를 경험했다.
비즈니스 로직상 RuntimeException이 발생하여 트랜잭션이 커밋되지 않고 롤백이 이루어지는 시나리오를 테스트 중이었는데, 계속 시나리오데로 흘러가지 않고 커밋이 이루어지는 경험을 했다.
아래는 비즈니스 로직과 테스트 코드이다.
@Transactional
public Member create(MemberCreateRequest request) {
Member member = request.toEntity();
Member saveMember = memberRepository.save(member);
if (saveMember.getAge() == 20) {
throw new RuntimeException("나이가 20살 이상인 회원만 등록할 수 있습니다.");
}
return saveMember;
}
@Test
void 트랜잭션_전파_테스트() {
assertThrows(RuntimeException.class, () -> {
memberService.create(MemberCreateRequest.builder()
.name("홍길동")
.age((byte) 20)
.build());
});
assertThat(memberRepository.findAll()).hasSize(0);
}
나의 예상은 멤버가 저장되지 않고 롤백되어 findAll() 메서드 결과가 0이 된다고 생각했다. 그러나 겨로가는 롤백이 되지 않고 커밋되어 멤버가 저장이 되었다.
내가 알기론 @Transactional 안에서 언체크 예외가 발생한다면 해당 트랜잭션은 롤백이 된다고 알고있다. 그러면 문제는 테스트 코드이다. 여러가지 시도하고 찾아본 결과 문제는 @DataJpaTest였다. JPA를 이용하여 테스트 하기위해서 @DataJpaTest를 사용하였는데 이 어노테이션을 자세히 보면 @Transactional이 붙은 걸 볼 수 있다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@Transactional //문제!
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface DataJpaTest {
그리고 @DataJpaTest 문서를 보면
Annotation for a JPA test that focuses only on JPA components.
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to JPA tests.
By default, tests annotated with @DataJpaTest are transactional and roll back at the end of each test. They also use an embedded in-memory database (replacing any explicit or usually auto-configured DataSource). The @AutoConfigureTestDatabase annotation can be used to override these settings.
이렇게 설명하고 있다. 이걸 간단히 설명하면 @DataJpaTest는 단순히 JPARepository를 테스트할 때 사용하는 어노테이션이라고 생각된다. 따라서 Serive 계층 테스트에는 적절하지 않다고 생각된다. 또한 @DataJpaTest는 모든 메서드마다 @Transactional이 적용되기 때문에 트랜잭션 전파가 일어나서 롤백이 되지 않는 것이다.
나는 첫번쨰로 서비스 계층을 테스트하기위해서 테스트 코드를 작성 중이었기 때문에 @DataJpaTest 대신 @SrpingBootTest를 사용하였다. 그 결과 정상적으로 롤백 테스트가 성공되었다. 또다른 방법으로는 테스트에 @Transactional의 전파 옵션을 꺼주는 것이다. @Transactional의 전파 옵션을 NEVER, NOT_SUPPORTED, SUPPORT로 변경하여 테스트 코드를 작성하면 정상적으로 테스트가 동작하였다.
테스트 코드에 @Transactional을 사용하는 것이 좋지 않다는 글을 종종 본다. 왜냐하면 쓸데 없는 @Transactional이 생길 수 있고 flush되어야 처리되는 Audit 정보가 처리되지 않는다. 또한 레이지로딩이 안될 곳에서도 된다는 등의 단점들이 존재한다. 그러나 DB에 대한 처리가 모두 롤백이 되기 때문에 내 생각에는 이 장점만으로도 타 단점을 가릴 수 있다고 생각한다. 이것또한 때와 환경에 맞춰 잘 사용하면 될 것 같다.