티스토리 뷰
@DataJpaTest를 사용하면서 데이터베이스 환경에 의한 문제 상황을 경험하였는데 @DataJpaTest에 대해 학습하며 이를 정리하고자 글을 작성하게 되었습니다.
@DataJpaTest
@DataJpaTest
는 JPA 구성 요소에 초점을 맞춰 슬라이스 테스트를 진행하기 위한 어노테이션이다. 애플리케이션이 동작하는 데 있어 필요한 모든 빈이 컨텍스트 환경에 등록되는 @SpringBootTest
와 달리 JPA 테스트와 관련된 빈들만 컨텍스트 환경에 등록한다. JPA 테스트 관련하여 필요한 빈들만 로드하기에 테스트 실행 시간을 줄이고 테스트 복잡성을 줄일 수 있다.
@DataJpaTest에는 다양한 어노테이션이 사용되는데 핵심적인 어노테이션에 대해 정리해 보았다.
[ @BootstrapWith ]
스프링 테스트에서 부트스트랩은 테스트를 위한 애플리케이션 컨텍스트를 생성하고 설정하는 과정을 말한다. 어노테이션에 지정된 구현체에 따라 컨텍스트를 설정하는 과정이 달라진다. @DataJpaTest에서는 DataJpaTestContextBootstrapper
구현체로 JPA 테스트를 위한 컨텍스트를 설정하고 있다.
[ @TypeExcludeFilters ]
애플리케이션 컨텍스트가 생성되는 동안 특정 타입의 빈을 제외시키는 역할을 한다. DataJpaTypeExcludeFilter
를 통해 JPA 외의 @Controller
, @Service
, @Component
등과 같은 어노테이션이 달린 클래스를 컴포넌트 스캔 대상에서 제외시키는 동작을 수행한다.
[ @AutoConfigureDataJpa ]
테스트 환경에서 Data JPA를 자동으로 구성하는 역할을 한다. 아래의 작업들을 수행한다.
- EntityManagerFactory를 생성하여 JPA 엔티티를 관리한다.
- 테스트 코드에서 트랜잭션 관리를 할 수 있도록 한다.
- Hibernate와 같은 JPA 구현체에 대한 구성을 수행한다.
- JPA 레포지토리를 스캔하고 애플리케이션 컨텍스트에 등록한다.
[ @AutoConfigureTestDatabase ]
서비스를 운영하는 데 사용하는 데이터베이스를 테스트에서도 사용할 수 없기에 테스트 데이터베이스 설정이 필요하다. 해당 테스트에 사용되는 데이터베이스를 자동으로 구성하는 역할을 한다. 아래와 같은 설정을 할 수 있다.
replace
: 어떤 유형의 데이터소스가 테스트 데이터소스로 교체될지 결정한다. 3가지 속성이 존재한다.ANY
: 기본값으로 어떤 데이터소스가 설정되더라도 테스트 데이터소스로 교체하게 된다.AUTO_CONFIGURED
: 스프링 부트에 의해 자동 구성된 데이터소스만 대체한다. 즉, 개발자가 구성한 데이터소스는 그대로 두고 스프링 부트가 자동으로 구성한 데이터 소스만 테스트 데이터소스로 교체하게 된다.NONE
: 어떠한 데이터소스도 테스트 데이터소스로 대체하지 않는다. 즉, 개발자가 구성한 데이터소스 설정을 그대로 사용하게 된다.
문제
spring:
datasource:
url: jdbc:h2:mem:test;MODE=MySQL
username: sa
jpa:
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQL57Dialect
show-sql: true
위의 코드는 문제가 발생했던 애플리케이션의 설정 파일이다.
회원 정보에 대한 User
엔티티 객체와 이를 영속화시키기 위해 JpaRepository를 상속받아 UserRepository
를 구현했다. UserRepository의 동작을 테스트하기 위해 @DataJpaTest 어노테이션을 사용하여 테스트했을 때 아래와 같은 에러를 확인할 수 있었다.
회원 정보 저장을 위해 INSERT 쿼리를 날리려고 하는데 USER 테이블이 존재하지 않아서 발생하는 에러이다. h2 데이터베이스를 사용하기 위한 url
과 username
을 올바르게 작성하였음에도 데이터베이스에 USER 테이블이 생기지 않은 이유가 무엇일까? 조금 더 구체적인 상황을 확인하기 위해 상위 에러 메시지도 확인해 보았다.
org.springframework.dao.InvalidDataAccessResourceUsageException: nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
에러 문구를 통해 잘못된 데이터 액세스가 발생했음을 알 수 있다.
조금 더 상위로 올라가서 메시지를 확인하였더니 USER 테이블을 생성하는 쿼리는 나갔지만 DDL이 올바르지 않다는 에러 메시지를 확인할 수 있었다. 원인을 알게 되었으니 이를 해결해 보도록 하자.
해결
문제의 원인은 @AutoConfigureTestDatabase
어노테이션과 관련이 있다. @DataJpaTest는 replace 속성이 기본값인 ANY이기 때문에 어떠한 데이터소스를 설정하더라도 테스트 데이터베이스 환경으로 설정을 변경한다.
spring:
datasource:
url: jdbc:h2:mem:test;MODE=MySQL
username: sa
jpa:
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQL57Dialect
show-sql: true
설정한 데이터소스를 다시 한번 확인하면 h2 데이터베이스를 인메모리에서 동작하는 MySQL 모드로 설정하였다. dialect 설정을 통해 SQL 구문도 MySQL 구문으로 생성되도록 하였는데, 여기서 문제가 발생하는 것이었다. SQL은 MySQL 구문으로 생성되지만 테스트 데이터베이스는 설정한 데이터소스가 아닌 테스트 데이터베이스 환경으로 교체되었기에 SQL 구문을 정상적으로 실행시킬 수 없어 테이블이 정상적으로 생성되지 않는 것이다.
이 문제를 해결하기 위한 방법은 2가지가 존재한다.
Dialect
spring:
datasource:
url: jdbc:h2:mem:test;MODE=MySQL
username: sa
jpa:
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.H2Dialect
show-sql: true
dialect를 H2 구문으로 수정한다. h2 데이터베이스는 H2 구문으로 생성된 SQL을 정상적으로 실행시킬 수 있기에 JPA가 생성한 DDL을 실행시킬 수 있다. 따라서 테이블이 정상적으로 생성되고 테스트가 통과되는 것을 확인할 수 있다.
@AutoConfigureTestDatabase
@AutoConfigureTestDatabase 설정을 수정해서 문제를 해결할 수 있다. 현재 replace.ANY
속성으로 설정되어 있어 테스트 데이터베이스 환경으로 변경되어 설정했던 MySQL 모드 데이터베이스를 사용할 수 없는 것이다. 이를 replace.NONE
으로 수정하여 설정한 데이터소스를 사용할 수 있다면 MySQL 구문도 정상적으로 실행할 수 있기에 테이블이 정상적으로 생성된다.
@DataJpaTest를 사용하는 클래스 레벨에 수정하고자 하는 설정을 가진 어노테이션 사용을 통해 설정을 수정할 수 있다. MySQL 모드의 h2 데이터베이스로 테스트가 진행되기에 MySQL 구문도 실행시킬 수 있고 테이블이 정상적으로 생성되고 테스트도 통과하는 것을 확인할 수 있다.
'Spring > Spring Boot' 카테고리의 다른 글
[Spring Boot] Apple OAuth 로그인 구현(1) - Apple OAuth 정리 (0) | 2023.08.06 |
---|---|
[Spring Boot] 스프링 부트 프로젝트에 Swagger 적용 (10) | 2023.07.16 |
[Spring Boot] 프로메테우스, 그라파나를 이용한 스프링 부트 모니터링 (3) | 2023.07.02 |
[Spring] 영속성 컨텍스트는 어디까지 유지되는가 (4) | 2023.03.12 |
[Spring] Asciidoctor를 통해 Spring Rest Docs 자동 생성 (2) | 2023.02.04 |