Spring

Kotlin 에서 Spring 으로 테스트할 때 주의사항

AlgoPoolJa 2023. 2. 14. 20:19

문제 상황

  • kotlin 에서 JUnit 을 사용하여 테스트를 구성했지만 org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter 오류가 발생했습니다.
  • @SpringBootTest
    class MemberRepositoryTest(
       val memberRepository: MemberRepository,
    ) {
       @Test
       @DisplayName("멤버 삭제")
       fun deleteMember() {    
           val member = memberRepository.save(MemberFactory.of("test"))  
           memberRepository.delete(member)
      }
    }

해결 방법

우선 junit.jupiter 에서 궁금한 점이 생겼습니다.

Junit 5 의 경우 크게 3가지 부분으로 이루어져 있습니다.

  • Jupiter
    • JUnit platform 을 구현한 구현체로, JUnit 5의 구현체입니다.
  • Vintage
    • JUnit platform 을 구현한 구현체로, JUnit3, 4 의 구현체 입니다.
  • JUnit Platform
    • 실행할 수 있는 엔진을 포함하고 여러 도구에 일관성있는 API 를 제공합니다.

그다음 ParameterResolver 의 역할이 궁금했습니다.

생성자 매개변수의 경우 main 패키지에 있는 코드들은 Spring IoC 컨테이너가 이를 해결합니다. 하지만 test 패키지에서는 생성자 매개변수 관리를 Jupiter 가 담당하게 됩니다. 그래서 @Autowired 를 명시적으로 선언해주어야 Jupiter 가 Spring Contrainer 에게 빈 주입을 요청 할 수 있습니다.

테스트 프레임워크에서 프레임워크의 주체는 Jupiter 이기 때문에 생성자 주입이라 한들 @Autowired 애노테이션이 명시되지 않은 객체는 의존성 주입을 받을 수 없게 됩니다.

Jupiter 는 생성자 매개변수를 처리할 ParameterResolver 를 찾지만 이를 다룰 수 있는 ParameterResolver 를 찾을 수 없고, 예외를 던지게 됩니다. 따라서 @Autowired 를 사용해 Jupiter가 Spring Container 에게 빈 주입을 요청하도록 @Autowired 애노테이션을 추가하면 해결됩니다.

@SpringBootTest
class MemberRepositoryTest @Autowired constructor(
    val memberRepository: MemberRepository,
) {
    @Test
    @DisplayName("멤버 삭제")
    fun deleteMember() {    
        val member = memberRepository.save(MemberFactory.of("test"))   
        memberRepository.delete(member)
    }
}

향상된 방법

JUnit5 부터는 생성자를 통한 의존관계 주입이 가능해졌습니다.

Spring 에서 제공하는 @TestConstructor 애노테이션을 사용하면 됩니다. 이를 사용하면 테스트 클래스 생성자에 들어있는 필드들에 의존관계를 주입해 줄 수 있습니다. 주입해줄 수 있는 방법은 크게 2가지 방법이 있습니다.

  1. ALL
    • 테스트 생성자의 모든 파라미터는 자동 주입이 됩니다.(생성자의 모든 파라미터에 @Autowired 가 붙어있다고 생각하면 편합니다.)
  2. ANNOTATED
    • @Autowired, @Qualifer, @Value 로 어노테이션이 붙어져 있는 생성자 필드에 대해 각각이 자동 주입됩니다.
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class MemberRepositoryTest (
    val memberRepository: MemberRepository,
) {
    @Test
    @DisplayName("멤버 삭제")
    fun deleteMember() {    
        val member = memberRepository.save(MemberFactory.of("test"))   
        memberRepository.delete(member)
    }
}