프레임워크/자바 스프링

JPA 테스트 코드 작성시 UPDATE Query 생성이 안되네?

hyeseong-dev 2024. 5. 28. 18:51

JUnit5에서 DataJpaTest를 사용한 테스트와 데이터베이스 상호 작용

1. DataJpaTest 어노테이션과 테스트 진행

DataJpaTest 어노테이션은 JUnit5에서 JPA 연결 테스트를 작성하는 데 사용되는 편리한 기능입니다. 이 어노테이션은 다음과 같은 특징을 제공합니다.

  • 자동 트랜잭션 관리: 각 테스트 메서드를 시작하고 종료할 때 자동으로 트랜잭션을 시작하고 롤백합니다.
  • 스프링 부트 테스트 설정: 스프링 부트 테스트 환경을 자동으로 구성하여 JPA 및 기타 관련 빈을 사용할 수 있도록 합니다.
  • 데이터베이스 준비 및 정리: 테스트 전에 테스트 데이터베이스를 준비하고 테스트 후 정리합니다.

DataJpaTest를 사용한 테스트 진행 과정은 다음과 같습니다.

  1. 테스트 클래스 작성: @DataJpaTest 어노테이션을 사용하여 테스트 클래스를 정의합니다.
  2. 필요한 빈 주입: 테스트 클래스의 생성자 또는 테스트 메서드에서 필요한 JPA 리포지토리 및 기타 빈을 주입합니다.
  3. 테스트 메서드 작성: 각 테스트 케이스를 나타내는 @Test 어노테이션이 있는 메서드를 작성합니다.
  4. 테스트 로직 구현: 테스트 메서드 내에서 테스트 로직을 구현합니다. 이 로직에는 데이터베이스 조작, 엔티티 검증, 예외 처리 등이 포함될 수 있습니다.
  5. 테스트 실행: JUnit5 테스트 러너를 사용하여 테스트 클래스를 실행합니다.

2. 객체 Update 테스트 및 SQL UPDATE Query 생성

문제

@DataJpaTest를 사용하여 객체 Update 테스트를 작성했지만, 단순히 repository의 save 메서드를 사용하면 SQL UPDATE Query가 생성되지 않습니다. 이는 @DataJpaTest가 테스트 메서드마다 자동으로 트랜잭션을 시작하고 롤백하기 때문입니다.

해결 방법

SQL UPDATE Query를 생성하고 실제 데이터베이스 변경을 확인하기 위해서는 다음과 같은 방법을 사용할 수 있습니다.


1. @DataJpaTest 제거 및 직접 트랜잭션 관리

@DataJpaTest 어노테이션을 제거하고 테스트 메서드 내에서 직접 트랜잭션을 시작하고 커밋하거나 롤백해야 합니다. @Transactional 어노테이션 또는 EntityManager 메서드를 사용하여 트랜잭션을 관리할 수 있습니다.

@Test
@Transactional // 트랜잭션 시작 및 커밋
void updateArticleTest() {
    // Given
    Article article = articleRepository.findById(1L).orElseThrow();
    String updatedHashtag = "#springboot";
    article.setHashtag(updatedHashtag);

    // When
    articleRepository.save(article); // 엔티티 변경 내용 저장

    // Then
    Article savedArticle = articleRepository.findById(1L).orElseThrow();
    assertThat(savedArticle.getHashtag()).isEqualTo(updatedHashtag);
}

2. @Transactional(propagation = Propagation.NOT_SUPPORTED) 사용

@DataJpaTest 어노테이션을 유지하면서 @Transactional(propagation = Propagation.NOT_SUPPORTED)를 사용할 수 있습니다. 이 어노테이션은 테스트 메서드 내부에서 발생하는 트랜잭션만 영향을 받고, 테스트 범위 외부의 트랜잭션에는 영향을 미치지 않습니다. 즉, 테스트 실행 후 데이터베이스 변경 사항이 유지됩니다.

@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED) // 트랜잭션 롤백 방지
void updateArticleTest() {
    // Given
    Article article = articleRepository.findById(1L).orElseThrow();
    String updatedHashtag = "#springboot";
    article.setHashtag(updatedHashtag);

    // When
    articleRepository.save(article); // 엔티티 변경 내용 저장

    // Then
    Article savedArticle = articleRepository.findById(1L).orElseThrow();
    assertThat(savedArticle.getHashtag()).isEqualTo(updatedHashtag);
}

3. @EntityManager.persist() 사용

앞선 두 가지 방법은 트랜잭션 관리 방식을 변경하는 것이었습니다. 세 번째 방법인 @EntityManager.persist() 는 트랜잭션 관리를 완전히 우회하여 객체를 직접 데이터베이스에 저장하는 방법입니다.

장점

  • 트랜잭션 관리 오버헤드를 줄일 수 있습니다.
  • 테스트 케이스에서 더 세밀하게 데이터 저장 시점을 제어할 수 있습니다.

단점

  • JPA가 제공하는 캐싱 및 변경 감지 기능을 사용하지 않게 됩니다.
  • 직접 트랜잭션을 관리하지 않으면 데이터 무결성 문제가 발생할 수 있습니다.

사용 방법

  1. 테스트 클래스에 @Autowired EntityManager 주입:
    테스트 클래스에서 @Autowired 어노테이션을 사용하여 EntityManager 객체를 주입합니다.
  2. 엔티티 변경:
    테스트 로직에서 수정하고자 하는 엔티티를 가져와 필요한 속성을 변경합니다.
  3. @EntityManager.persist() 호출:
    EntityManager 객체의 persist() 메서드를 사용하여 변경된 엔티티를 데이터베이스에 저장합니다.

예시 코드

@Test
void updateArticleTest(@Autowired EntityManager entityManager) {
    // Given
    Article article = articleRepository.findById(1L).orElseThrow();
    String updatedHashtag = "#springboot";
    article.setHashtag(updatedHashtag);

    // When
    entityManager.persist(article); // 트랜잭션 관리 없이 직접 저장

    // Then (선택적)
    Article savedArticle = articleRepository.findById(1L).orElseThrow();
    assertThat(savedArticle.getHashtag()).isEqualTo(updatedHashtag);
}

주의 사항

@EntityManager.persist() 를 사용할 경우, 테스트 메서드 내에서 직접 데이터 변경을 관리해야 합니다. jména 다음과 같은 사항을 고려해야 합니다.

  • 트랜잭션 관리: 필요한 경우, entityManager.flush() 메서드를 사용하여 변경 내용을 데이터베이스에 반영하고, 테스트 종료 전에 entityManager.clear() 메서드를 사용하여 영속 컨텍스트를 초기화하는 것이 좋습니다.
  • 데이터 무결성: 테스트 데이터 준비 및 정리가 필요할 수 있으며, 테스트 간의 데이터 간섭을 방지하기 위해 주의하여 설계해야 합니다.

결론

하지만 사실 원하는 것은 한 가지입니다. DB에 데이터가 남을 필요도 없습니다. 오히려 원하지 않습니다. 테스트 이후 데이터가 모두 사라지기를 원합니다.
하지만 Update SQL Query는 만들어져 console에서 식별하기 위해서는 save()메서드 대신 saveAndFlus() 메서드를 사용해야 합니다.