tools & libs/단위테스트(junit, spock)

[spring test] 2. Spring Data Jpa Test

  • -
반응형

이번 포스트에서는 Spring Data Jpa에 대한 테스트를 위한 @DataJpaTest 사용법에 대해 알아보자.

 

@DataJpaTest

 

애너테이션 분석

@DataJpaTest는 Spring Data Jpa 테스트를 위한 slice test 애너테이션이다.  @DataJpaTest는 database 및 jpa와 관련된 다양한 자동 설정을 로딩한다.

@Target(ElementType.TYPE)
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface DataJpaTest { 
	@PropertyMapping("spring.jpa.show-sql")
	boolean showSql() default true;
}

@DataJpaTest는 아래와 같은 특징을 갖는다.

  • @TypeExcludeFilters(DataJpaTypeExcludeFilter.class): JPA 테스트를 위한 빈을 @Entity, JpaRepository 구현체, EntityManager 구현체등으로 한정한다.
  • @Transactional: 테스트가 끝나면 자동으로 rollback 된다.
  • @AutoConfigurerCache: 기본 CacheType으로 CacheType.NONE을 설정해서 데이터를 캐싱하지 않는다.
  • @AutoConfigurerTestDataJpa: JpaRepository를 사용할 수 있는 환경을 로드한다.
  • @AutoConfigurerTestDatabase: 프로젝트에 설정된 DB 대신 메모리 기반의 내장 DB를 사용해서 테스트 한다. 따라서 메모리 기반의 H2등이 의존성에 추가되어야 한다. 만약 원래의 DB를 사용하려면 @AutoConfigureTestDatabase 의 replace 속성을 Replace.NONE으로 재정의 해야 한다.
  • @AutoConfigurerTestEntityManager: TestEntityManager를 사용할 수 있게 한다.
  • spring.jpa.show-sql 속성: 자동으로 true로 설정된다. 만약 변경하고 싶다면 showSql 속성을 false로 설정하면 된다.

 

테스트 환경 구성

JPA를 위한 설정 파일을 다음과 같이 작성해보자. 기본적으로 embedded 기반으로 h2를 사용하고 있고 show-sql 속성이 설정되지 않았음을 기억해 두자.

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:~/spring_board
    username: quietjun
    password: quietjun
  jpa:
    hibernate:
      ddl-auto: create

다음은 Entity로 Board를 작성해 보자.

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long boardNo;
    @NonNull
    private String writer;
    @NonNull
    private String content;
}

BoardEntity에 대한 CRUD를 처리할 BoardRepository는 다음과 같다.

public interface BoardRepository extends JpaRepository<Board, Long>{ }

 

사용되는 DB 확인

먼저 @DataJpaTest를 사용하면 어떻게 테스트가 진행되는지 검사해 보자.

@DataJpaTest
//@AutoConfigureTestDatabase(replace = Replace.NONE)
@Slf4j
public class T_9321_DataJpaTest {

    @Autowired
    DataSource ds;
    
    @Test
    public void inspect() throws SQLException {
        // DataJpaTest의 경우 기본은 memory db 이용
        log.debug("connection type: {}", ds.getConnection());  
        // repo는 proxy기반의 AOP로 동작함
        log.debug("repo type: {}", repo.getClass().getName());
    }

앞서 application.yml에서 datasource의 url을 jdbc:h2:~/spring_board로 설정했는데 이 방식은 embedded server 방식이다. 하지만 위 테스트를 실행해 보면 connection type이 jdbc:h2:mem:XXX 형식의 memory db임을 알 수 있다. 만약 원래 설정된 database를 사용하려면 @AutoConfigureTestDatabase(replace = Replace.NONE)를 추가하면 된다.

 

insert test와 sql 확인

다음으로 새로운 Entity를 저장하고 출력되는 sql을 확인해 보자.

@Autowired
BoardRepository repo;

@Test
public void insertTest() {
  // given
  int preSize = repo.findAll().size();
  Board board = Board.builder().writer("hong").content("new content").build();
  // when
  repo.save(board);
  // then
  assertEquals(repo.findAll().size(), preSize + 1);
}

위 테스트를 실행하면 다음의 로그가 출력된다.

Hibernate: select b1_0.board_no,b1_0.content,b1_0.writer from board b1_0
Hibernate: insert into board (content,writer,board_no) values (?,?,default)
Hibernate: select b1_0.board_no,b1_0.content,b1_0.writer from board b1_0

show-sql과 관련된 설정을 하지 않았음에도 편리하게 로그가 잘 출력되고 있다.

 

TestEntityManager 활용

데이터를 조회/수정/삭제하기 위해서는 대상 Entity가 있어야 하는데 이때 @BeforeEach에서 entity를 저장하고 테스트하는 것이 일반적이다. 그런데 이때 JpaRepository의 save를 이용하면 @BeforeEach와 @Test가 1차 캐시를 동일하게 사용하므로 생각하는 쿼리의 실행이 보이지 않는다. 예를 들어 @BeforeEach(insert) -> @Test(select)를 테스트 하는 경우 select 쿼리는 실행되지 않는다.

이때 TestEntityManager를 사용하면 강제로 EntityManager의 close를 호출할 수 있어 쿼리의 실행을 명확히 확인할 수 있다.

@Autowired
TestEntityManager tem;

private Board board;

@BeforeEach
public void setup() {
  board = Board.builder().writer("jang").content("sample content").build();
  tem.persist(board);
  tem.clear();
}

@Test
public void testSelect() {
  // given
  long boardNo = board.getBoardNo();
  // when
  Board selected = repo.findById(boardNo).get();
  // then
  assertEquals(selected, board);
}
Hibernate: insert into board (content,writer,board_no) values (?,?,default)
Hibernate: select b1_0.board_no,b1_0.content,b1_0.writer from board b1_0 where b1_0.board_no=?

TestEntityManager는 EntityManager의 helper class로 한 줄에 persist/flush/find 할 수 있는 기능을 가지고 있다.

 

@Sql

테스트에 사용되는 데이터를 위 테스트에서 처럼 @BeforeEach에서 설정할 수도 있는데 가끔 sql 형태의 스크립트로 작성해 놓고 테스트 시 호출하는 방법을 쓰기도 한다. 이때는 @Sql 애너테이션을 사용할 수 있다.

@DataJpaTest
@Sql(scripts = {"classpath:init-sql/data.sql"})
public class T_9321_DataJpaTest { .. }

// src/test/resources/init-sql/data.sql
insert into board (writer, content) values ('jang','sample');

물론 위의 script도 @BeforeEach와 마찬가지로 테스트 시마다 호출되며 rollback 대상이다.

 

 

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.