8.6 KiB
Repository Test 예시
좋은 예시
예시 1. 기본 repository test는 @DataJpaTest로 시작한다
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void findByEmail_returnsUser() {
// given
User user = new User("a@test.com", "active");
userRepository.save(user);
// when
Optional<User> result = userRepository.findByEmail("a@test.com");
// then
assertThat(result).isPresent();
}
}
좋은 이유:
- JPA slice만 좁게 로딩한다
- repository 자체를 테스트 대상으로 유지한다
- full application context를 불필요하게 띄우지 않는다
Spring Boot는 @DataJpaTest가 JPA components에 초점을 맞추고, 엔티티와 repository를 스캔한다고 설명한다.
예시 2. 제약 위반은 flush()까지 가서 검증한다
@DataJpaTest
class UserRepositoryConstraintTest {
@Autowired
private UserRepository userRepository;
@Test
void save_duplicateEmail_throwsExceptionOnFlush() {
userRepository.save(new User("dup@test.com", "active"));
userRepository.save(new User("dup@test.com", "active"));
assertThatThrownBy(() -> userRepository.flush())
.isInstanceOf(Exception.class);
}
}
좋은 이유:
- unique constraint 위반이 실제 DB 동기화 시점에 드러난다는 점을 반영한다
save()만 보고 통과한 테스트를 방지한다- 영속성 경계의 실제 실패 시점을 검증한다
Hibernate의 영속성 컨텍스트는 1차 캐시로 동작하므로, DB 의미를 보려면 flush가 중요하다. TestEntityManager도 persistAndFlush 같은 helper를 제공한다.
예시 3. 실제 재조회 의미를 검증할 때는 flush() 후 clear()를 사용한다
@DataJpaTest
class UserRepositoryReloadTest {
@Autowired
private UserRepository userRepository;
@Autowired
private TestEntityManager em;
@Test
void findActiveUsers_excludesDeletedRows() {
userRepository.save(new User("a@test.com", "active", null));
userRepository.save(new User("b@test.com", "active", OffsetDateTime.now()));
em.flush();
em.clear();
List<User> users = userRepository.findAllByDeletedAtIsNullOrderByIdDesc();
assertThat(users).extracting(User::getEmail)
.containsExactly("a@test.com");
}
}
좋은 이유:
- 1차 캐시가 아니라 실제 DB round-trip 이후 결과를 검증한다
- soft delete predicate 같은 조회 semantics를 더 신뢰도 높게 확인한다
TestEntityManager를 보조 도구로만 사용한다
Hibernate는 persistence context가 transaction-scoped first-level cache로 동작한다고 설명하고, TestEntityManager는 test에서 persist/flush/find helper를 제공한다고 설명한다.
예시 4. PostgreSQL 특화 query는 실제 DB 계열에서 검증한다
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class PostgresUserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void findRecentlyCreatedUsers_worksWithPostgresSpecificQuery() {
// given / when / then
}
}
좋은 이유:
- 임베디드 DB 대체를 끄고 실제 DB 계열을 사용하도록 의도를 드러낸다
- PostgreSQL native query나 dialect 의존 쿼리를 더 신뢰도 높게 검증할 수 있다
- repository test의 범위는 유지하면서 DB 의미를 맞춘다
Spring Boot는 @DataJpaTest가 기본적으로 임베디드 DB를 구성할 수 있고, 실제 DB를 선호하면 @AutoConfigureTestDatabase로 제어할 수 있다고 설명한다.
예시 5. commit이 정말 필요한 경우에만 예외적으로 commit 테스트를 쓴다
@DataJpaTest
@Commit
class UserRepositoryCommitTest {
@Autowired
private UserRepository userRepository;
@Test
void savesDataThatMustBeObservedAfterCommit() {
userRepository.save(new User("commit@test.com", "active"));
}
}
좋은 이유:
- 기본 rollback 규칙을 알고, 필요한 경우에만 예외를 사용한다
- commit 이후에만 보이는 DB 효과를 검증하는 목적이 분명하다
- rollback이 기본, commit은 예외라는 기준을 지킨다
Spring 테스트는 transactional test를 기본 rollback하고, @Commit/@Rollback으로 이를 바꿀 수 있다고 설명한다.
나쁜 예시
예시 1. repository만 보는데 @SpringBootTest를 기본값으로 쓴다
@SpringBootTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
}
나쁜 이유:
- repository 경계만 볼 테스트에 full application context를 띄운다
- 테스트 범위와 비용이 과도하다
@DataJpaTest가 더 적합한 기본값이다
Spring Boot는 @DataJpaTest를 JPA slice test로 제공하고, 일반 @Component는 로드하지 않는다고 설명한다.
예시 2. 제약 테스트를 flush 없이 작성한다
@DataJpaTest
class UserRepositoryConstraintTest {
@Autowired
private UserRepository userRepository;
@Test
void save_duplicateEmail_throwsException() {
userRepository.save(new User("dup@test.com", "active"));
assertThatThrownBy(() ->
userRepository.save(new User("dup@test.com", "active"))
).isInstanceOf(Exception.class);
}
}
나쁜 이유:
- 실제 DB 제약 위반이 아직 드러나지 않을 수 있다
- 영속성 컨텍스트 안에서 테스트가 가짜로 통과하거나 실패 시점이 늦어질 수 있다
- 이런 검증은 보통
flush시점까지 가야 한다
Hibernate의 persistence context는 1차 캐시로 동작하므로, DB 의미를 드러내려면 flush가 중요하다.
예시 3. 재조회 검증인데 clear() 없이 같은 엔티티만 본다
@DataJpaTest
class UserRepositoryReloadTest {
@Autowired
private UserRepository userRepository;
@Test
void findById_readsUpdatedState() {
User user = userRepository.save(new User("a@test.com", "active"));
user.changeStatus("inactive");
User found = userRepository.findById(user.getId()).orElseThrow();
assertThat(found.getStatus()).isEqualTo("inactive");
}
}
나쁜 이유:
- 같은 persistence context 안의 같은 엔티티 인스턴스를 다시 본 것일 수 있다
- 실제 DB에서 다시 읽은 결과인지 보장되지 않는다
- 조회 semantics 검증으로는 신뢰도가 낮다
Hibernate는 Session/EntityManager가 first-level cache를 유지한다고 설명한다.
예시 4. PostgreSQL 특화 native query를 임베디드 DB만으로 신뢰한다
@DataJpaTest
class NativePostgresQueryTest {
@Autowired
private UserRepository userRepository;
@Test
void works() {
userRepository.runPostgresSpecificQuery();
}
}
나쁜 이유:
- 테스트 DB가 운영 DB 의미를 충분히 재현하지 못할 수 있다
- dialect 차이, 함수, JSONB, partial index 전제, locking clause 같은 부분은 놓치기 쉽다
- DB 특화 query는 실제 DB 계열 검증이 더 적합하다
Spring Boot는 @DataJpaTest가 임베디드 DB를 기본 구성할 수 있고, 실제 DB를 쓰려면 @AutoConfigureTestDatabase로 제어할 수 있다고 설명한다.
예시 5. repository test 안에서 service 정책까지 같이 검증한다
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Autowired
private UserService userService;
}
나쁜 이유:
- 테스트 범위가 repository 경계를 벗어난다
- slice 목적과 맞지 않는다
- service 정책 검증은 상위 통합 테스트 책임이다
@DataJpaTest는 JPA components에만 초점을 맞추고 일반 컴포넌트를 로드하지 않는 slice다.
예시 6. repository test에서 repository 대신 EntityManager만 직접 사용한다
@DataJpaTest
class UserRepositoryTest {
@PersistenceContext
private EntityManager em;
@Test
void test() {
em.persist(new User("a@test.com", "active"));
em.createQuery("select u from User u", User.class).getResultList();
}
}
나쁜 이유:
- repository를 검증하겠다면서 repository를 거의 사용하지 않는다
- 결국 repository contract가 아니라 JPA API 자체를 테스트하게 된다
EntityManager는 보조 도구로만 쓰는 편이 적절하다
Spring Boot는 TestEntityManager를 repository/JPA test의 보조 도구로 제공한다고 설명한다. 중심은 repository여야 한다.