[Spring] @DataJpaTest 는 자동 롤백이 기본 정책이다

Hocaron·2023년 9월 21일
2

Spring

목록 보기
32/44


@DataJpaTest 로 OneToOne 연관관계가 있는 객체를 저장 후 조회하는데, owner 에는 null 이 들어와서 테스트 코드는 실패한다.
owner 에는 setUp() 에서 저장한 person 이 조회되지 않는걸까?

@DataJpaTest 는 @Transactional을 포함하고 있다

진짜일까?

그래서 수행되는 하나의 테스트는 하나의 트랜잭션 안에서 이루어진다

    @Test
    @Transactional
    void getOwnerTest() {
        personId = personRepository.save(Person.of("name1")).personId();
        animalId = animalRepository.save(Animal.of(personId)).animalId();
        
        var animal = animalRepository.findById(animalId).get();

        assertThat(animal.owner().personId()).isEqualTo(personId);
        assertThat(animal.owner().name()).isEqualTo("name1");
    }

setUp(), getOwnerTest() 로 나누어져 있지만, 사실은 위와 같이 하나의 트랜잭션에서 수행되는 것과 같다.

다른 트랜잭션에서 수행되도록 할 수 있을까?

트랜잭션을 분리하자

    @Test
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    void getOwnerTest() {
        personId = personRepository.save(Person.of("name1")).personId();
        animalId = animalRepository.save(Animal.of(personId)).animalId();

        var animal = animalRepository.findById(animalId).get();

        assertThat(animal.owner().personId()).isEqualTo(personId);
        assertThat(animal.owner().name()).isEqualTo("name1");
    }

Propagation.NOT_SUPPORTED 로 기존 Transaction 없이 수행되도록 한다.
이 경우에는 자동 롤백처리가 안 되니 조심하자.

Propagation 속성

@Transactional Propagation 속성설명
REQUIRED (기본값)기존에 생성된 Transaction이 있으면 참여하고 없다면 새로운 Transaction을 생성합니다.
REQUIRES_NEW항상 새로운 Transaction을 생성합니다.
SUPPORTS기존에 생성된 중인 Transaction이 있을 때만 참여하고 없다면 Transaction 없이 진행합니다.
NOT_SUPPORTED기존에 생성된 Transaction 이 있든 말든 Transaction 없이 진행합니다.
MANDATORY이미 진행 중인 Transaction이 있으면 참여하고, 기존에 생성된 Transaction 이 없다면 예외를 발생시킵니다.
NESTED이미 진행 중인 Transaction이 있다면 중첩으로 Transaction을 생성하여 진행합니다.

@SpringBootTest 를 사용하자

@SpringBootTest 를 사용해서 다른 트랜잭션에서 수행되도록 한다.

@Transactional 으로 롤백하는 원리를 살펴보자

어떻게 자동으로 롤백처리를 해주는거지?

스프링 테스트 프레임워크에서는 TransactionalTestExecutionListener.java 가 활성화 되어 Transactional을 관리한다. TransactionalTestExecutionListener는 클래스 또는 메서드 레벨에 적용한 @Transactional을 감지해 자동으로 메서드에 트랜잭션을 건다.


beforeTestMethod() 에서 isRollback() 를 호출한다.


isRollback() 은 기본적으로 true 를 반환한다.


afterTestMethod() 에서 txContext.endTransaction() 를 호출한다. 이때, defaultRollback / flaggedRollback 필드가 true 인 것을 알 수 있다.

TransactionContext.javaflaggedRollback가 true 인 경우 this.transactionManager.rollback(this.transactionStatus) 을 호출하고, 위에서 봤던 로그를 찍게된다.

Rolled back transaction for test: [DefaultTestContext@ff684e1 testClass = RelationTest

만약 롤백을 하고 싶지 않다면 @Rollback(value = false) 옵션을 추가하자

    @Test
    @Rollback(value = false)
    void getOwnerTest() {
        var animal = animalRepository.findById(animalId).get();

        assertThat(animal.owner().personId()).isEqualTo(personId);
        assertThat(animal.owner().name()).isEqualTo("name1");
    }

해당 옵션의 기본값은 true 이다. 롤백하고 싶지 않은 경우 false 로 옵션을 변경하면 롤백이 되지 않는다.

TransactionalTestExecutionListener.java 의 isRollback() 을 보면 우리가 준 rollback 어노테이션의 옵션값인 false 가 들어가는 것을 볼 수 있다.

정리

  • @DataJpaTest 는 @Transactional을 포함하고 있다.
  • @DataJpaTest 는 하나의 테스트가 하나의 트랜잭션으로 수행된다.
  • 테스트 클래스 / 메서드에 @Transactional 이 존재하면, TransactionalTestExecutionListener가 자동으로 트랜잭션을 걸어준다.
  • 기본 설정은 롤백이다.

위의 테스트 코드는 여기

profile
기록을 통한 성장을

0개의 댓글