Kotlin 에서 Spring 으로 테스트할 때 주의사항
문제 상황
- 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가지 방법이 있습니다.
- ALL
- 테스트 생성자의 모든 파라미터는 자동 주입이 됩니다.(생성자의 모든 파라미터에 @Autowired 가 붙어있다고 생각하면 편합니다.)
- 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)
}
}