21가지 Repository를 만들고, 각각 잘 작동을 하는지 테스트를 해보았다.
내가 만든 21개의 Entitiy와 Repository 인터페이스가 정말 DB와 잘 통신하는지 확신할 수 없다. 그래서 테스트 코드를 통해 실제 DB에 쿼리를 실행시켜 증명하는 과정이 필요한 것이다.
실제로 이번 테스트를 하면서 갖가지 오류들이 발생해서 어떤 부분에서 오류가 발생했는지 찾는데 애를 먹었지만, 만약 테스트를 하지 않고 그대로 개발을 진행하고 나중에 테스트해보았다면, 어디서 오류가 났는지 찾기가 더 힘들었을 것이다.
@DataJpaTest는 JPA Repository 테스트를 위한 어노테이션이다.
@SpringBootTest(모든 Bean을 로드하여 무겁다)와 달리, @DataJpaTest는 JPA 계층(DB연결, Entity, Repository)에 관련된 Bean들만 가져와 로드한다. 그래서 테스트가 매우 가볍고 빠르다.
@DataJpaTest가 해주는 일은 다음과 같다.
1. JPA에 필요한 모든 설정(DataSource, EntityManager 등)을 로드한다.
2. 우리가 만든 모든 JpaRepository인터페이스(@...Repository)를 스캔하여 Bean으로 등록한다.
3. 모든 테스트 메소드를 @Transaction으로 감싼다.
4. 테스트가 끝나면, 해당 트랜잭션을 자동으로 롤백한다. - 자동으로 롤백을 해주기 때문에, 테스트 하고도 DB에 남지 않아 따로 정리를 해주지 않아도 된다.
@BeforeEach로 사전 데이터 준비Post를 테스트하려면 User가, Comment를 테스트하려면 User와 Post가 미리 DB에 존재해야 한다.@BeforeEach어노테이션이 붙은 setUp() 메소드는 각 @Test 메소드가 실행되기 직전에 매번 호출된다.// PostRepositoryTest.java
@Autowired
private UserRepository userRepository;
private User savedTestUser;
@BeforeEach
void setUp() {
// 테스트에 필요한 'User'를 미리 저장
User testUser = User.builder()...build();
savedTestUser = userRepository.saveAndFlush(testUser);
}
@Test
void savePostTest() {
// 1. 방금 setUp()에서 만든 savedTestUser를 사용
Post post = Post.builder().user(savedTestUser)...build();
postRepository.save(post);
}
@Test
void deletePostTest() {
// 2. 여기서도 새로 실행된 setUp()의 savedTestUser를 사용
Post post = Post.builder().user(savedTestUser)...build();
// ...
}
savedAndFlush() : save()와 달리 DB에 즉시 INSERT쿼리를 실행하고, 생성된 ID를 객체에 반영해준다. @BeforeEach에서 데이터를 확실히 준비할 때 유용하다.given-when-then패턴
테스트 코드를 given(준비), when(실행), then(검증) 이렇게 3단계로 나누어 작성하면 가독성이 높아진다.
AssertJ (assertThat)
assertThat(foundUser.getName()).isEqualTo("테스트유저");처럼, AssertJ 라이브러리를 사용하면 JUnit의 assertEquals보다 더 직관적이고 풍부한 검증(예 : isEmpty(), hasSize(2))이 가능하다.
@DataJpaTest는 편리하지만, 기본 설정 때문에 만날 수 있는 함정이 있다.
.class파일을 찾지 못했다.@DataJpaTest는 기본적으로 H2를 띄우려고 한다. 하지만 Flyway 스크립트는 BIGSERIAL, "User"(따옴표) 등 PostgreSQL 전용 문법으로 작성되어 있어서, H2에서는 실행이 실패했다.@AutoConfigureTestDatabase(replace = NONE) 어노테이션을 추가했더니 해결되었다. 이 어노테이션은 DB 설정을 H2로 교체하지 말고 그냥 application.properties에 있는 실제 DB를 사용하라는 의미이다.@DataJpaTest는 JPA 관련 Bean만 로드하므로, 메인 클래스(DungeongApplication)에 있던 @EnableJpaAuditing 설정을 로드하지 않는다. 그 결과, save()를 해도 createdAt필드가 null로 들어가는 문제가 생긴다.JpaAuditingConfig.java)을 만들고, 테스트 클래스에 @Import(JpaAuditingConfig.class) 어노테이션을 추가했다. 이 어노테이션은 JPA 테스트를 로드할 때, 이 JpaAuditingConfig 설정 파일도 추가로 로드해달라는 의미이다.relation "user" does not exist)"User"(맨 앞만 대문자 U)로 생성했는데, JPA가 insert into "user"(소문자 U)로 쿼리를 생성했다.spring.jpa.hibernate.naming.physical-strategy : JPA가 엔티티 이름을 마음대로 소문자로 바꾸지 않고, @Table(name = "\"User\"")에 적힌 그대로 쓰도록 강제한다.spring.jpa.properties.hibernate.globally_quoted_identifiers=true : 모든 테이블/컬럼명에 항상 쌍따옴표를 붙여서 쿼리를 생성하도록 강제한다.@Where 미작동 (column "deleted_at" does not exist)globally_quoted_identifiers=true를 켰는데도, @Where(clause = "DELETED_AT IS NULL") 어노테이션 안의 문자열에는 쌍따옴표가 자동으로 붙지 않았다.User, Post 등)의 @Where, @SQLDelete 어노테이션 내부 문자열에 직접 이스케이프된 쌍따옴표(\"...\")를 하드코딩했다.@Where(clause = "DELETED_AT IS NULL") -> @Where(clause = "\"DELETED_AT\" IS NULL)