Backend

@DataJpaTest 롤백은 id까지 초기화해주지 않는다

jbilee 2025. 5. 17. 22:45

@DataJpaTest 애노테이션은 @Transactional이 포함되어 있어, 각 테스트가 끝나면 트랜잭션이 롤백된다. 테스트 도중 영속화된 데이터가 전부 초기화되니, 자연스럽게 매번 깨끗한 상태의 데이터베이스를 사용한다고 생각했다.

 

사실 모든 것이 초기화되는 것은 아니었다. 아래처럼 EntityManager로 데이터를 persist하는 똑같은 테스트 코드를 중복으로 넣어봤을 때, 테스트를 각각 따로 돌리면 전부 성공하지만 클래스 단위로 전체 테스트를 돌리면 둘 중 하나는 매번 실패하는 것을 확인했다.

// 모든 테스트를 한꺼번에 돌려본다
@DataJpaTest
class PersistenceTest {

    @Autowired
    EntityManager em;

    // 성공하는 테스트
    @Test
    void persist1() {
        Customer customer = new Customer("Rowin", "Chester");

        em.persist(customer);

        Customer persisted = em.find(Customer.class, 1L);

        assertThat(persisted.getId()).isEqualTo(1L);
    }

    // 실패하는 테스트--persisted 값이 null이다
    @Test
    void persist2() {
        Customer customer = new Customer("Aaron", "Ruthburn");

        em.persist(customer);

        Customer persisted = em.find(Customer.class, 1L);

        assertThat(persisted.getId()).isEqualTo(1L);
    }
}

 

persist2() 테스트에서는 데이터가 영속화되지 않는 건가?

 

그렇지 않다. 데이터는 제대로 영속화되지만, 부여된 id가 1이 아니었던 것이다. id가 2인 Customer 데이터를 조회하면 테스트하고자 했던 대로 Aaron이라는 이름을 가진 Customer가 조회된다.

 

@Transactional 애노테이션으로 인해 데이터 수정이 롤백되긴 하지만, 데이터베이스 자체가 초기화되는 것은 아니다. 때문에 id 카운터는 계속 유지되는 것이다.

 

식별자 카운터까지 초기화하는 방법

id가 초기화되지 않는다는 이유로 테스트마다 increment된 식별자를 수동으로 트랙킹한다면 휴먼에러가 발생할 수밖에 없다. 데이터베이스 자체를 초기화하려면 @DirtiesContext 애노테이션을 쓰거나 테스트용 .sql 파일 혹은 테스트 코드에 테이블 카운터를 리셋하는 방법을 고려해볼 수 있다.

 

@DirtiesContext는 테스트 메서드마다 Spring 컨텍스트(ApplicationContext)를 새로 생성해 테스트하도록 설정할 수 있다. @DataJpaTest도 ApplicationContext를 생성해 동작하는 것이기 때문에 @DirtiesContext 애노테이션을 적용할 수 있다.

 

다만 이 방법은 IoC 컨테이너를 생성하는 오버헤드로 인해 테스트 속도가 느려진다는 단점이 있다.

 

컨텍스트 대신 데이터베이스 카운터만 초기화할 수도 있다. 만약 @Sql 애노테이션으로 테스트 전용 SQL 파일을 설정해서 사용하고 있다면 해당 파일에 카운터를 초기화하는 SQL문을 추가하기만 하면 된다.

ALTER TABLE customer ALTER COLUMN id RESTART WITH 1;

 

자바 코드로 초기화한다면 위 SQL문을 @AfterEach 메서드에서 호출하면 된다.

@AfterEach
void resetId() {
    em.createNativeQuery("ALTER TABLE customer ALTER COLUMN id RESTART WITH 1").executeUpdate();
}