[Spring Boot] DAO(Repository) 테스트 코드 작성

임원재·2025년 1월 13일
0

SpringBoot

목록 보기
11/19
post-thumbnail
  • Spring Boot DAO(Data Access Object)인 Repository 계층의 단위 테스트하는 방법을 정리해보았다.
  • Repository 계층은 다른 레이어의 의존이 없이 독립적인 계층이므로 Controller, Service계층의 단위 테스트보다 간단한 주입만을 요한다.
  • Repository의 각 메서드를 테스트하게 되면, 데이터베이스의 데이터를 변경하게 되므로, 기본적으로 각 메서드들은 트랜잭션 안에서 실행된다.
  • 즉, 테스트가 종료되면 트랜잭션이 rollback되므로 데이터베이스의 데이터 변경을 걱정하지 않아도 된다.

@DataJpaTest

  • 해당 애노테이션은 JPA 테스트에 필요한 EntityManager, JpaRepository, Hibernate설정 등 JPA관련한 빈을 로드하도록 해준다.
  • Spring 통합 테스트 시 사용하는 @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)는 인메모리 데이터베이스 사용하도록 설정한다.

@ActiveProfiles("test")

  • 해당 애노테이션을 클래스에 적용하여 테스트 관련 db설정을 분리 가능하다.
  • 아래와 같이 application-test.properties를 적용할 수 있다.
# 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

@Import

  • @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로 직접 주입하여 테스트를 진행한다.
  • 해당 테스트클래스를 실행했을 때 어떤 db에서 해당 테스트가 이루어지는지 확인하기 위해 jdbcTemplate를 주입하여 url를 출력하였다.

@BeforeEach / @AfterEach

  • 각 메서드를 실행하기 전, 후로 실행할 메서드를 정의 가능하다.
  • 메서드마다 독립성이 존재해야 하므로 해당 애노테이션을 사용해 repository를 비우거나 EntityManager를 주입하여 영속성 컨텍스트를 flushclear하는 용도로 사용한다.

@Order

  • @Order애노테이션을 이용해 테스트 메서드의 실행 순서를 정할 수 있다.
  • Order(n)의 숫자가 작을수록 우선순위가 크다.
  • 해당 애노테이션을 사용하기 위해 테스트클래스에
    ``@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
    를 붙여야 사용 가능하다.

0개의 댓글