Framework & Library/SpringDataAccess

@DataJpaTest를 사용한 단위 테스트에서 @Autowired Repository의 빈이 등록되지 않는 문제 해결하기.

ruu++ 2024. 5. 10. 00:25

개발환경

  • Spring Boot 3.2.3
  • JDK 17
  • Gradle 8.5
  • queryDsl 5.0.0

개요

  1. JPA 테스트 및 @Repository 어노테이션을 사용한 클래스의 DB 계층 단위 테스트가 목표였습니다.
  2. TestDB만을 위한 환경을 구축하고 싶었습니다.

1. 문제점

@ExtendWith(SpringExtension.class)  
@DataJpaTest  
@ActiveProfiles(value = "test")  
@AutoConfigureTestDatabase(replace = Replace.NONE)  
public class RepositoryTest {  
    @Autowired  
    MemberRepository memberRepository;  

    @Test  
    public void test(){  
        memberRepository.findById(1L);  
    }  
}
  • @Repository 어노테이션으로 작성된 memberRepository클래스의 빈을 생성하지 못하는 에러가 발생했습니다.

1 - 1. Error

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.ruu.bootthymeleafjpa.study.RepositoryTest': Unsatisfied dependency expressed through field 'memberRepository': No qualifying bean of type 'org.ruu.bootthymeleafjpa.domain.MemberRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
  • @DataJpaTest는 JPA만을 테스트하기 위한 단위 테스트로 JPA 환경 설정에 특화된 스프링 테스트컨텍스트를 가지고 있다고 합니다.
  • 이 때, 빈을 로드하는데 @Repository, @Controller, @Service 등의 스프링 빈을 컨테이너에 등록하지 않습니다.
  • 이러한 이유로 @Repository라는 어노테이션을 찾지 못해서 빈의 생성자를 정의하지 못한 것입니다.

1 - 2. 해결

@DataJpaTest(includeFilters = 
			 @ComponentScan.Filter(
				 type = FilterType.ANNOTATION, 
				 classes = Repository.class))  
  • DataJpaTest에 컴포넌트 스캔으로 @Repository 어노테이션을 찾고 정보를 읽어 빈 생성이 가능하도록 필터를 추가했습니다.

2. 최종 코드

2 - 1. Annotation

import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;  
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;  
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 org.springframework.test.context.ActiveProfiles;  
  
@DataJpaTest(includeFilters = 
	@ComponentScan.Filter(type = 
    	FilterType.ANNOTATION, 
    	classes = 	Repository.class))  
@ActiveProfiles(value = "test")  
@AutoConfigureTestDatabase(replace = Replace.NONE)  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
public @interface RepositoryTest{   
}

2 - 2. TestCode

@RepositoryTest  
public class HelloRepository {  
  	
    @Autowired  
    private MemberRepository memberRepository;  
    @Autowired  
    private TestEntityManager entityManager;  
  
    Long savedId;  
    String name;  
  
    @BeforeEach  
    public void setUp(){  
        Member member = new Member();  
        member.setUsername("memberA");  
  
        name = member.getUsername();  
        savedId = memberRepository.save(member);  
  
        entityManager.flush();  
        entityManager.clear();  
    }  
      
    @Test  
    public void testMember() {  
  
        Member findMember = memberRepository.findById(savedId);  
  
        Assertions.assertThat(findMember.getId()).isEqualTo(savedId);  
        Assertions.assertThat(findMember.getUsername()).isEqualTo(name);  
    }  
}

3. 환경설정

test/resources/application.yml

spring:  
  datasource:  
    url: jdbc:h2:tcp: # db url  
    username:  
    password:  
    driver-class-name: org.h2.Driver  
  jpa:  
    hibernate:  
      ddl-auto: create 
    properties:  
      hibernate:  
        show_sql: true  
        format_sql: true  
    database-platform: org.hibernate.dialect.H2Dialect # 방언 설정  
  profiles:  
    active: test # test라는 active를 읽습니다.

참고 자료
인프런 질의 응답
스택오버플로우