안녕하세요. J4J입니다.
이번 포스팅은 Layer별 테스트 코드 작성하는 방법 첫 번째인 JPA를 이용한 Repository 테스트하는 방법에 대해 적어보는 시간을 가져보려고 합니다.
JpaRepository 테스트
JPA를 이용하는 환경에서 repository 테스트를 수행할 때 가장 대표적으로 해볼 수 있는 테스트는 JpaRepository 사용에 대한 테스트입니다.
JpaRepository를 테스트하기 위해서는 jpa에서 제공해 주는 @DataJpaTest 어노테이션을 활용하면 되는데 해당 어노테이션 내부를 확인해 보면 다음과 같이 다양한 설정 값들이 들어 있는 것을 볼 수 있습니다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface DataJpaTest {
...
}
@AutoConfigureDataJpa와 같이 jpa를 사용하여 테스트할 수 있는 설정 값들을 뿐만 아니라 @Transactional과 같이 테스트에 사용된 데이터들이 실제 DB에는 적용되지 않도록 도와주는 설정도 포함되어 있습니다.
또한 repository 관련 테스트에 필요한 설정들만 들어있기 때문에 repository 테스트를 수행할 때 repository 동작과 관련 없는 controller, service 등을 포함한 모든 설정을 하지 않습니다.
그러다 보니 repostiory 테스트를 수행할 때 테스트를 무겁게 만드는 spring 동작을 위한 모든 설정을 하지 않아 더 빠른 속도로 테스트를 수행할 수 있게 도와줍니다.
이처럼 해당 어노테이션에 테스트를 위해 사용할 수 있는 다양한 것들이 담겨있기 때문에 부가적인 설정 없이도 JpaRepository 테스트를 수행할 수 있도록 도와줍니다.
이제는 테스트 코드를 구성해 보겠습니다.
간단하게 JpaRepository에서 제공해 주는 findById 메서드 테스트를 한다고 가정하면 다음과 같이 코드가 작성될 수 있습니다.
[ 1. Entity 확인 ]
package com.jforj.repositorytest.entity;
import jakarta.persistence.*;
import lombok.*;
@ToString
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "student")
public class StudentEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long no;
private String name;
private int age;
private long schoolNo;
@Builder
public StudentEntity(String name, int age, long schoolNo) {
this.name = name;
this.age = age;
this.schoolNo = schoolNo;
}
}
[ 2. JpaRepository 확인 ]
package com.jforj.repositorytest.repository;
import com.jforj.repositorytest.entity.StudentEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StudentJpaRepository extends JpaRepository<StudentEntity, Long> {
}
[ 3. 테스트 코드 작성 ]
package com.jforj.repositorytest.repository;
import com.jforj.repositorytest.entity.StudentEntity;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import static org.junit.jupiter.api.Assertions.assertNotNull;
// JPA 테스트 코드 구성 설정을 도와주는 어노테이션
@DataJpaTest
// DataJpaTest에 의해 테스트용 데이터베이스가 자동으로 구성되지만, 실제 사용되는 DB를 사용하고 싶은 경우 다음과 같이 설정
// 실제 사용되는 DB를 사용하지 않는 경우 테스트용 데이터베이스 구성을 위한 추가 설정 필요
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class StudentRepositoryTest {
@Autowired
private StudentJpaRepository studentJpaRepository;
@Test
void findByIdTest() {
// given
StudentEntity savedStudentEntity =
studentJpaRepository
.save(
StudentEntity
.builder()
.name("givenName")
.age(20)
.schoolNo(1L)
.build()
);
// when
StudentEntity foundStudentEntity =
studentJpaRepository
.findById(savedStudentEntity.getNo())
.get();
// then
assertNotNull(foundStudentEntity);
}
}
[ 4. 테스트 결과 확인 ]
@Repository 테스트
이번에는 JpaRepository가 아닌 @Repository 어노테이션을 이용하여 repository 클래스 파일을 자체적으로 구성했을 때를 얘기해 보겠습니다.
일반적으로 JPA를 사용하고 있는데 @Repository 어노테이션을 이용하여 repository 클래스 파일을 따로 구성하는 경우는 repository에서 발생될 수 있는 기능을 명확하게 메서드화 시킬 때 사용됩니다.
그리고 JpaRepostiory 같은 경우는 repository 클래스 내부에 의존성 주입되어 사용됩니다.
위에서 JPA 테스트를 위해 사용되고 있던 @DataJpaTest는 보시다시피 JpaRepository에서 테스트할 수 있는 기본 설정들을 제공해주고 있습니다.
하지만 지금처럼 JpaRepository를 바로 사용하지 않고 @Repository 어노테이션을 이용하여 repository 클래스 파일을 구성한 것에 대한 설정까지 기본적으로 지원해주지 않습니다.
그래서 @DataJpaTest 어노테이션에 @Repository 어노테이션이 설정된 곳까지 테스트를 수행할 수 있도록 추가적인 설정을 해줘야 합니다.
간단하게 모든 이름 정보를 조회하는 테스트 코드를 작성해 보겠습니다.
아래 설정들은 위의 설정을 기반으로 한 추가적인 변경점입니다.
[ 1. Repository 클래스 추가 ]
package com.jforj.repositorytest.repository;
import com.jforj.repositorytest.entity.StudentEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.stream.Collectors;
@Repository
@RequiredArgsConstructor
public class StudentRepository {
private final StudentJpaRepository studentJpaRepository;
public List<String> selectNames() {
List<StudentEntity> studentEntities = studentJpaRepository.findAll();
return studentEntities
.stream()
.map(StudentEntity::getName)
.collect(Collectors.toList());
}
}
[ 2. 테스트 코드 변경 ]
package com.jforj.repositorytest.repository;
import com.jforj.repositorytest.entity.StudentEntity;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Repository;
import static org.junit.jupiter.api.Assertions.assertNotNull;
// JPA 테스트 코드 구성 설정을 도와주는 어노테이션 ( + @Repository 관련 설정 포함)
@DataJpaTest(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
// DataJpaTest에 의해 테스트용 데이터베이스가 자동으로 구성되지만, 실제 사용되는 DB를 사용하고 싶은 경우 다음과 같이 설정
// 실제 사용되는 DB를 사용하지 않는 경우 테스트용 데이터베이스 구성을 위한 추가 설정 필요
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class StudentRepositoryTest {
@Autowired
private StudentJpaRepository studentJpaRepository;
@Autowired
private StudentRepository studentRepository;
@Test
void selectNamesTest() {
// given
String givenName = "givenName";
studentJpaRepository.save(
StudentEntity
.builder()
.name(givenName)
.age(20)
.schoolNo(1L)
.build()
);
// when
String filterName =
studentRepository
.selectNames()
.stream()
.filter(name -> name.equals(givenName))
.findFirst()
.get();
// then
assertNotNull(filterName);
}
}
[ 3. 테스트 결과 확인 ]
Querydsl 테스트
이번에는 querydsl을 사용하는 경우에 대한 테스트 방법에 대해 소개드리겠습니다.
querydsl도 기본적으로 @Repository 테스트와 같이 querydsl 사용을 위한 repository 클래스 파일을 생성합니다.
그래서 @Repository 테스트를 하기 위한 설정과 크게 차이가 없습니다.
하지만 @Repository 테스트에서 @DataJpaTest에 추가적인 설정을 한 것처럼 테스트 코드에서 querydsl 사용을 위한 설정이 더 필요합니다.
querydsl을 적용할 때 JPAQueryFactory 설정을 위한 클래스 파일을 추가하고는 하는데, 해당 설정 파일을 테스트 코드 용도로 활용하기 위해 추가적으로 만들어 주시면 됩니다.
querydsl 테스트하는 방법도 @Repository와 동일하게 모든 이름을 조회하는 테스트를 작성해 보겠습니다.
이 또한 위의 설정 값들을 기반으로 한 추가적인 변경점입니다.
[ 1. Repository 클래스 변경 ]
package com.jforj.repositorytest.repository;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
import static com.jforj.repositorytest.entity.QStudentEntity.studentEntity;
@Repository
@RequiredArgsConstructor
public class StudentRepository {
private final JPAQueryFactory jpaQueryFactory;
public List<String> selectNames() {
return jpaQueryFactory
.select(studentEntity.name)
.from(studentEntity)
.fetch();
}
}
[ 2. test 폴더 하위에 Querydsl 설정 추가 ]
package com.jforj.repositorytest.config;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
@TestConfiguration
public class QuerydslTestConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
[ 3. 테스트 코드 변경 ]
package com.jforj.repositorytest.repository;
import com.jforj.repositorytest.config.QuerydslTestConfig;
import com.jforj.repositorytest.entity.StudentEntity;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Repository;
import static org.junit.jupiter.api.Assertions.assertNotNull;
// JPA 테스트 코드 구성 설정을 도와주는 어노테이션 ( + @Repository 관련 설정 포함)
@DataJpaTest(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
// DataJpaTest에 의해 테스트용 데이터베이스가 자동으로 구성되지만, 실제 사용되는 DB를 사용하고 싶은 경우 다음과 같이 설정
// 실제 사용되는 DB를 사용하지 않는 경우 테스트용 데이터베이스 구성을 위한 추가 설정 필요
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
// querydsl 사용을 위한 설정 파일 import
@Import(QuerydslTestConfig.class)
class StudentRepositoryTest {
@Autowired
private StudentJpaRepository studentJpaRepository;
@Autowired
private StudentRepository studentRepository;
@Test
void selectNamesTest() {
// given
String givenName = "givenName";
studentJpaRepository.save(
StudentEntity
.builder()
.name(givenName)
.age(20)
.schoolNo(1L)
.build()
);
// when
String filterName =
studentRepository
.selectNames()
.stream()
.filter(name -> name.equals(givenName))
.findFirst()
.get();
// then
assertNotNull(filterName);
}
}
[ 4. 테스트 결과 확인 ]
Repository 테스트를 위한 커스텀 어노테이션
지금까지 작성된 내용들을 확인해 보면 repository와 관련된 다양한 테스트 코드를 모두 작성할 수 있습니다.
하지만 개발되는 프로젝트에 사용되는 repository의 개수가 많아질수록 느껴지는 불편함이 생길 수 있습니다.
현재 작성되어 있는 테스트 코드를 살펴보면 @DataJpaTest와 같이 repository 테스트를 위한 설정 값만 담긴 어노테이션이 사용되고 있지만 그 외 설정들이 담기기 때문에 다음과 같이 여러 개의 어노테이션이 작성된 것을 볼 수 있습니다.
// JPA 테스트 코드 구성 설정을 도와주는 어노테이션 ( + @Repository 관련 설정 포함)
@DataJpaTest(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
// DataJpaTest에 의해 테스트용 데이터베이스가 자동으로 구성되지만, 실제 사용되는 DB를 사용하고 싶은 경우 다음과 같이 설정
// 실제 사용되는 DB를 사용하지 않는 경우 테스트용 데이터베이스 구성을 위한 추가 설정 필요
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
// querydsl 사용을 위한 설정 파일 import
@Import(QuerydslTestConfig.class)
class StudentRepositoryTest {
...
}
그리고 이런 설정은 프로젝트 내부에서 모두 사용되기 때문에 모든 repository 테스트를 작성하는 클래스 파일에 포함되어 있어야 합니다.
이처럼 매번 설정하는 것은 불편함을 발생시킬 수 있고 이를 해결하기 위해 우리 만의 repository 테스트 설정을 도와주는 커스텀 어노테이션을 만들어 볼 수 있습니다.
커스텀 어노테이션은 다음과 같이 만들어서 사용해 볼 수 있고 더 편리하게 repository 테스트를 위한 환경 구성을 하는 결과를 볼 수 있습니다.
[ 1. test 폴더 하위에 커스텀 어노테이션 추가 ]
package com.jforj.repositorytest.annotation;
import com.jforj.repositorytest.config.QuerydslTestConfig;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Repository;
import java.lang.annotation.*;
// annotation이 class, interface, record 등의 선언에 사용될 수 있도록 설정
@Target(ElementType.TYPE)
// annotation이 runtime 단계에서 사용될 수 있도록 설정
@Retention(RetentionPolicy.RUNTIME)
// annotation이 javadoc 문서화에 표현될 수 있도록 설정
@Documented
// annotation이 부모 클래스에 선언되어 있을 때 자식 클래스에도 상속되도록 설정
@Inherited
// JPA 테스트 코드 구성 설정을 도와주는 어노테이션 ( + @Repository 관련 설정 포함)
@DataJpaTest(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
// DataJpaTest에 의해 테스트용 데이터베이스가 자동으로 구성되지만, 실제 사용되는 DB를 사용하고 싶은 경우 다음과 같이 설정
// 실제 사용되는 DB를 사용하지 않는 경우 테스트용 데이터베이스 구성을 위한 추가 설정 필요
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
// querydsl 사용을 위한 설정 파일 import
@Import(QuerydslTestConfig.class)
public @interface RepositoryTest {
}
[ 2. 테스트 코드 변경 ]
package com.jforj.repositorytest.repository;
import com.jforj.repositorytest.annotation.RepositoryTest;
import com.jforj.repositorytest.entity.StudentEntity;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import static org.junit.jupiter.api.Assertions.assertNotNull;
// 모든 설정을 커스텀 어노테이션으로 한 개로 대체
@RepositoryTest
class StudentRepositoryTest {
@Autowired
private StudentJpaRepository studentJpaRepository;
@Autowired
private StudentRepository studentRepository;
@Test
void selectNamesTest() {
// given
String givenName = "givenName";
studentJpaRepository.save(
StudentEntity
.builder()
.name(givenName)
.age(20)
.schoolNo(1L)
.build()
);
// when
String filterName =
studentRepository
.selectNames()
.stream()
.filter(name -> name.equals(givenName))
.findFirst()
.get();
// then
assertNotNull(filterName);
}
}
[ 3. 테스트 결과 확인 ]
이상으로 Layer별 테스트 코드 작성하는 방법 첫 번째인 JPA를 이용한 Repository 테스트하는 방법에 대해 간단하게 알아보는 시간이었습니다.
읽어주셔서 감사합니다.
'Spring > SpringBoot' 카테고리의 다른 글
[SpringBoot] Layer별 테스트 코드 작성하기 (3) - Controller 테스트 (2) | 2024.02.07 |
---|---|
[SpringBoot] Layer별 테스트 코드 작성하기 (2) - Service 테스트 (0) | 2024.02.04 |
[SpringBoot] Spring Validation을 이용한 유효성 검증하기 (1) | 2024.01.29 |
[SpringBoot] Spring Boot 3 버전 이후 gradle에 swagger 사용 환경 설정하기 (0) | 2024.01.27 |
[SpringBoot] Spring Boot 3 버전 이후 gradle에 querydsl 사용 환경 설정하기 (0) | 2024.01.22 |
댓글