개요
프로젝트 진행중에 QueryDsl을 사용하는 CustomRepository를 만들었다. 여러 엔티티를 JOIN하여 데이터를 조회할 예정이였기 때문에 JpaRepository에 상속하지 않고 @Repository 어노테이션을 붙여 스프링 빈으로 등록하여 사용하고 있었다. 코드는 다음과 같다.
Repository
@RequiredArgsConstructor
@Repository
public class AdminRepository implements ApplimentSearchRepository {
private final JPAQueryFactory queryFactory;
@Override
public Optional<ApplimentMember> findApplimentMemberById(Long applyId) {
...
}
@Override
public List<ApplimentMemberResponse> findAllApplimentMembers(final Long recruitmentId, final ApprovalStatus status) {
...
}
@Override
public List<ApprovedMemberResponse> findAllApprovedMembers(final Long recruitmentId) {
...
}
public Long countAttend(final Long recruitmentId, final Long userId, final Attendance attendance) {
...
}
private BooleanExpression attendanceEq(Attendance attendance) {
...
}
private BooleanExpression statusEq(ApprovalStatus status) {
...
}
}
public interface ApplimentSearchRepository {
Optional<ApplimentMember> findApplimentMemberById(Long applyId);
List<ApplimentMemberResponse> findAllApplimentMembers(final Long recruitmentId, final ApprovalStatus status);
List<ApprovedMemberResponse> findAllApprovedMembers(final Long recruitmentId);
}
문제점
이전까지 Repository의 테스트는 @DataJpaTest를 사용하여 테스트 코드를 작성하고 있었는데, 문제는 @DataJpaTest의 특성으로 인해 발생했다.
@DataJpaTest의 특징은 다음과 같다.
- JPA에 관련된 요소들만 테스트하기 위한 어노테이션으로 JPA 테스트에 관련된 설정들만 적용해준다.
- 메모리상에 내부 데이터베이스를 생성하고 @Entity 클래스들을 등록하고 JPA Repository 설정들을 해준다. 각 테스트마다 테스트가 완료되면 관련한 설정들은 롤백된다.
@DataJpaTest는 JPA에 관련된 설정들만 적용시켜주기 때문에 @Repository로 스프링 빈으로 등록한 AdminRepository 클래스는 주입되지 않는 것이다.
따라서, 다음과 같이 AdminRepositoryTest를 작성할 경우 AdminRepository 를 주입받을 수 없기 때문에 예외가 발생한다.
@AutoConfigureTestDatabase(replace = NONE)
@DataJpaTest
class AdminRepositoryTest {
@Autowired
private AdminRepository adminRepository;
...
}
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.moa.domain.member.AdminRepositoryTest': Unsatisfied dependency expressed through field 'adminRepository': No qualifying bean of type 'com.moa.domain.member.AdminRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
해결
이 문제는 특정 스프링 빈을 주입받지 못해 발생하는 것이기 때문에 @TestConfiguration 을 사용하여 테스트에 사용되는 클래스를 직접 빈으로 등록해주면 된다.
@TestConfiguration
public class TestQueryDslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
@Bean
public AdminRepository adminRepository() {
return new AdminRepository(jpaQueryFactory());
}
}
- AdminRepository는 JPAQueryFactory 를 주입받고 있기 때문에 JPAQueryFactory와 AdminRepository 를 빈으로 등록해주면 된다.
@AutoConfigureTestDatabase(replace = NONE)
@Import(TestQueryDslConfig.class)
@DataJpaTest
class AdminRepositoryTest {
@Autowired
private AdminRepository adminRepository;
...
}
이후 이 설정들을 @Import 어노테이션을 사용하여 적용 시켜주면 된다.
참고로 테스트에 사용되는 어노테이션들을 모아 커스텀하면 테스트 코드를 훨씬 더 깔끔하게 나타낼 수 있다.
// 테스트용 어노테이션 생성
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@AutoConfigureTestDatabase(replace = NONE)
@Import(TestQueryDslConfig.class)
@DataJpaTest
public @interface RepositoryTest {
}
// 테스트 코드
@RepositoryTest
class AdminRepositoryTest {
@Autowired
private AdminRepository adminRepository;
...
}
++) @DataJpaTest 에서 JpaAuditing 기능 활성화
@DataJpaTest 는 최소한의 빈만 불러오기 때문에 JpaAuditing 기능을 활성화하는 Configuration은 불러와지지 않는다.
따라서, 앞에서 생성한 TestQueryDslConfig 클래스에 @EnableJpaAuditing 어노테이션을 추가하자. 그럼 @CreatedDate나 @LastModifiedDate의 기능이 의도한대로 잘 동작할 것이다.
@EnableJpaAuditing
@TestConfiguration
public class TestQueryDslConfig {
...
}