
DAO(Data Access Object)인 Repository 계층의 단위 테스트하는 방법을 정리해보았다.Repository 계층은 다른 레이어의 의존이 없이 독립적인 계층이므로 Controller, Service계층의 단위 테스트보다 간단한 주입만을 요한다.Repository의 각 메서드를 테스트하게 되면, 데이터베이스의 데이터를 변경하게 되므로, 기본적으로 각 메서드들은 트랜잭션 안에서 실행된다.EntityManager, JpaRepository, Hibernate설정 등 JPA관련한 빈을 로드하도록 해준다.@SpringBootTest와 달리, MVC나 Service계층의 빈은 로드되지 않아 빠른 실행이 가능해진다.@DataJpaTest는 인메모리 데이터베이스를 사용한다. 이를 사용하기 위해 h2 db를 사용할 예정이다.build.gradle
// h2 database
testImplementation 'com.h2database:h2'
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class RepsitoryTest {
// ...
}
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)가 인메모리 데이터베이스 사용을 막는 역할을 한다.@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)는 인메모리 데이터베이스 사용하도록 설정한다.# datasource
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:testdb;
spring.jpa.hibernate.ddl-auto=create-drop
spring.datasource.username=sa
spring.h2.console.enabled=true
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.H2Dialect
@DataJpaTest애노테이션으로 JPA관련 빈을 로드하는 것만으로 Repository를 테스트할 수 없는 환경일 가능성이 있다.Entity에서 생성, 변경 시간을 캐치하기 위한 JpaAuditing같은 속성이 있다.@Configuration
@EnableJpaAuditing
public class JpaAuditingConfig {
}
repository를 실행하는데 전혀 문제 없는 기능이지만 해당 속성이 없으면 entity를 생성할 때 생성, 변경시간이 null이 되는 현상이 발생할 수 있다. (에러는 나지 않는다..)
중요한 예시로 Querydsl을 사용하여 repository를 구현했을 때는, 아래와 같은 Config를 생성해야한다.
@Configuration
public class QueryDslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
repository 실행에 있어 필수적인 설정이지만 @DataJpaTest를 사용해서 빈으로 로드되지 않기 때문에 직접 Import하는 이유라고 볼 수 있겠다.@DataJpaTest
@ActiveProfiles("test")
@Import({QueryDslConfig.class, JpaAuditingConfig.class})
class RepsitoryTest {
// ...
}
@DataJpaTest
@ActiveProfiles("test")
@Import({QueryDslConfig.class, JpaAuditingConfig.class})
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class LogCustomRepositoryTest {
@Autowired LogRepository logRepository;
@Autowired MemberRepository memberRepository;
@Autowired CategoryRepository categoryRepository;
@Autowired JdbcTemplate jdbcTemplate;
@AfterEach
void setUp() {
logRepository.deleteAll();
categoryRepository.deleteAll();
memberRepository.deleteAll();
}
@Test
@Order(1)
@DisplayName("check db")
void checkDatabaseUsed() throws SQLException {
String url = Objects.requireNonNull(jdbcTemplate.getDataSource()).getConnection().getMetaData().getURL();
System.out.println("db = " + url);
}
@Test
@Order(2)
@DisplayName("memberId로 사용자의 로그 찾기")
void findByMemberId() {
Member saveMember = memberRepository.save(MemberTestConvertor.createMemberInTest(1L));
Category saveCategory = categoryRepository.save(CategoryTestConvertor.createCategoryInTest(1L, saveMember));
logRepository.save(LogTestConvertor.createLogInTest(1L, saveCategory));
logRepository.save(LogTestConvertor.createLogInTest(2L, saveCategory));
List<Log> result = logRepository.findByMemberId(saveMember.getId());
Assertions.assertThat(result.size()).isEqualTo(2L);
// category 정보
Assertions.assertThat(result.get(0).getCategory().getId()).isEqualTo(1L);
Assertions.assertThat(result.get(1).getCategory().getId()).isEqualTo(1L);
// member 정보
Assertions.assertThat(result.get(0).getCategory().getMember().getId()).isEqualTo(1L);
Assertions.assertThat(result.get(1).getCategory().getMember().getId()).isEqualTo(1L);
}
}
@Test애노테이션을 붙여야하며, 시각적으로 확인하기 위해 추가로 @DisplayName을 사용하기도 한다.@Repository구현체들은 @Autowired로 직접 주입하여 테스트를 진행한다.jdbcTemplate를 주입하여 url를 출력하였다.repository를 비우거나 EntityManager를 주입하여 영속성 컨텍스트를 flush후 clear하는 용도로 사용한다.@Order애노테이션을 이용해 테스트 메서드의 실행 순서를 정할 수 있다.Order(n)의 숫자가 작을수록 우선순위가 크다.