본문 바로가기
Server/🌱 Spring Boot (java)

[Spring boot] DataJpaTest를 이용한 Repository 테스트와 여러 문제 해결

by 코딩하는 동현 2025. 1. 25.

JPA를 활용한 Repository 테스트를 작성할 때, 실제 데이터베이스와 상호작용하는 테스트가 필요할 때가 많습니다. 이를 위해 H2와 같은 인메모리 데이터베이스를 사용하여 테스트 환경을 구축할 수 있습니다. 이때, @SpringBootTest와 같은 전체 애플리케이션 컨텍스트를 로딩하는 대신, @DataJpaTest를 사용하여 JPA 관련 컴포넌트만 로드하는 방법이 효과적입니다. 또한, @Transactional을 사용하여 테스트 후 데이터베이스 상태가 롤백되도록 보장할 수 있습니다.

 

이 포스트에서 다루는 문제 해결은 다음과 같습니다. (트러블 슈팅 경험)

  1. DataJpaTest이용시 의존받는 일부 bean를 로드하지 못하는 문제 해결
  2. @Autowired 어노테이션을 이용시 해당 인스턴스의 상태공유가 안되는 문제

@DataJpaTest란?

@DataJpaTestSpring Data JPA 관련 컴포넌트만 로딩하여 테스트하는 어노테이션입니다. 이 어노테이션은 실제 데이터베이스와 상호작용하는 테스트를 빠르게 진행할 수 있도록 해주며, 데이터베이스 설정을 최소화하고 JPA와 관련된 기능만을 테스트할 수 있도록 도와줍니다.

 

주요 특징:

  • JPA 관련 컴포넌트만 로딩: 데이터베이스와 관련된 기능(예: 엔티티 매핑, 리포지토리 동작)을 테스트할 수 있습니다.
  • • 인메모리 데이터베이스 사용: 보통 H2와 같은 인메모리 데이터베이스를 사용하여 테스트합니다.
  • 테스트 데이터 롤백: 기본적으로 @Transactional이 활성화되어 테스트가 끝난 후 데이터베이스 상태가 롤백됩니다. 이를 통해 테스트 데이터가 실제 DB에 영향을 미치지 않도록 보장할 수 있습니다.

@Transactional이란?

 

@Transactional 어노테이션은 테스트 메서드에 적용하여 해당 메서드에서 발생한 모든 DB 작업을 하나의 트랜잭션으로 묶고, 테스트 종료 후 롤백되도록 합니다. 이로 인해 테스트 데이터가 실제 데이터베이스에 반영되지 않고, 테스트 후 데이터가 DB에 영향을 미치지 않도록 할 수 있습니다.

 

주요 특징:

  • 트랜잭션 롤백: 테스트가 끝난 후 트랜잭션을 롤백하여 DB 상태를 원래대로 되돌립니다.
  • 테스트 데이터 격리: 여러 테스트가 독립적으로 실행될 수 있도록 보장합니다.

 

@DataJpaTest는 기본적으로 @Transactional을 활성화하므로, 별도로 명시하지 않아도 테스트 후 롤백이 적용됩니다. 하지만, 이를 명확하게 보장하려면 @Transactional을 명시적으로 추가하는 것이 좋습니다.

 

예시코드

@DataJpaTest
@Transactional
public class TourRepositoryTest {

    @Autowired
    private TourRepository tourRepository;

    @Test
    public void testSaveAndFindTour() {
        // given
        Tour tour = new Tour("Paris", "A beautiful city");

        // when
        tourRepository.save(tour);
        Tour foundTour = tourRepository.findById(tour.getId()).orElse(null);

        // then
        assertNotNull(foundTour);
        assertEquals(tour.getName(), foundTour.getName());
    }
}

 

이 예시에서는 @DataJpaTest@Transactional을 사용하여, TourRepository를 테스트하고 있습니다. 테스트가 끝난 후, H2 데이터베이스에서 변경된 내용은 롤백되므로 실제 데이터베이스에 영향이 가지 않습니다.

 

@DataJpaTest@Transactional을 활용한 Repository 테스트는 JPA와 관련된 기능을 독립적으로 빠르게 테스트할 수 있도록 해주며, 테스트 데이터의 롤백을 보장하여 DB에 영향을 미치지 않도록 합니다. 이를 통해 테스트 환경을 분리하고, 테스트의 신뢰성을 높일 수 있습니다.


DataJpaTest시 의존받는 bean를 로드하지 못하는 문제 해결

아래 어노테이션 추가

@ContextConfiguration(classes = ProducerApplication.class)

  • 목적: Spring의 ApplicationContext를 설정할 때 특정 클래스들을 로드하도록 지정합니다.
  • 설명: 기본적으로 @DataJpaTest는 JPA 관련 설정만 로드하지만, ProducerApplication을 명시하여 ProducerApplication 클래스에서 정의한 빈들도 로드되게 합니다. 이는 필요할 경우 애플리케이션의 특정 설정이나 컴포넌트를 테스트에 포함시킬 수 있게 합니다.

@Import({FilterRepository.class, LocationLabeler.class})

목적: 테스트 클래스에서 사용할 특정 빈을 명시적으로 불러옵니다.

설명: @Import는 테스트 클래스에서 사용하고자 하는 다른 빈들을 불러올 수 있게 해줍니다.

 

@DataJpaTest
@ContextConfiguration(classes = ProducerApplication.class)
@EntityScan(basePackages = "entity")
@Import({FilterRepository.class, LocationLabeler.class})
@Transactional
public class FilterRepositoryTest {

	@Autowired
	private EntityManager entityManager;

	@Autowired
	private FilterRepository filterRepository;

	@Autowired
	private LocationLabeler locationLabeler;
    
    /// ..
}

 

이 예시에서는 FilterRepository와 LocationLabeler를 테스트에 포함시키기 위해 이 어노테이션을 사용하고 있습니다

@Import를 사용하면, 테스트 컨텍스트에 필요한 클래스를 명시적으로 추가할 수 있어 의존성을 주입하거나 사용할 수 있게 됩니다.

@ContextConfiguration은 ProducerApplication 클래스에서 정의된 빈과 설정들을 전반적으로 로드하지만, 테스트에서 사용해야 하는 모든 빈을 자동으로 로드하지는 않습니다.

@Import는 애플리케이션의 기본 설정 외에 특정 빈들만 따로 추가할 때 사용됩니다. FilterRepository와 LocationLabeler는 기본 설정에서 자동으로 로드되지 않거나 테스트에서 명시적으로 필요한 빈들이기 때문에 @Import를 통해 테스트 환경에 추가됩니다.


Test에서 @Autowired 어노테이션을 이용시 인스턴스의 상태공유가 안되는 문제 해결

원인

JUnit 5의 기본 동작은 각 테스트 메서드 실행 시 테스트 클래스의 새로운 인스턴스를 생성하는 것입니다. 이로 인해 클래스 필드에 선언된 상태가 테스트 메서드 간에 공유되지 않고 초기화됩니다. 테스트 메서드가 같은 객체의 상태를 공유해야 하는 경우, 이러한 동작이 문제를 일으킬 수 있습니다. 특히, @Autowired로 주입된 객체가 클래스의 상태에 의존하는 경우, 각 테스트 메서드에서 별도의 인스턴스가 생성되어 기대한 동작이 이루어지지 않을 수 있습니다.

해결

@TestInstance(TestInstance.Lifecycle.PER_CLASS)

  • 목적: 테스트 클래스 인스턴스를 모든 테스트 메서드에서 하나만 생성하도록 설정합니다.
  • 설명: 기본적으로 JUnit 5는 각 테스트 메서드 실행 시마다 테스트 클래스 인스턴스를 새로 생성합니다. 그러나 PER_CLASS를 사용하면 클래스 인스턴스를 한 번만 생성하고, 각 테스트 메서드는 그 인스턴스를 공유하게 됩니다. 이 설정을 사용하면, 클래스 필드에 선언된 상태를 테스트 간에 공유할 수 있어 성능을 개선할 수 있습니다. 예를 들어, @BeforeEach에서 초기화된 값들이 테스트 간에 지속될 수 있습니다.
@DataJpaTest
@ContextConfiguration(classes = ProducerApplication.class)
@EntityScan(basePackages = "entity")
@Import({FilterRepository.class, LocationLabeler.class})
@Transactional
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class FilterRepositoryTest {

	@Autowired
	private EntityManager entityManager;

	@Autowired
	private FilterRepository filterRepository;

	@Autowired
	private LocationLabeler locationLabeler;
반응형

댓글