Files
project-auth-server/docs/examples/testing/repository-test.md
T

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가 중요하다. TestEntityManagerpersistAndFlush 같은 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여야 한다.