@Autowired
를 생략해도 됐는데, JUnit으로 @SpringBootTest
테스트 코드를 작성할 때 같은 방식으로 하면 다음과 같은 에러가 발생한다.org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [nathan.test.repository.MemberRepository memberRepository] in constructor [public nathan.test.MemberRepositoryTest(nathan.test.repository.MemberRepository)].
MemberRepository
@Slf4j
@Repository
public class MemberRepository {
private final Map<Long, String> store = new ConcurrentHashMap<>();
private Long sequence = 0L;
public String save(Member member) {
member.setId(sequence++);
store.put(member.getId(), member.getName());
log.info("[Repository] member save: {} - {}", member.getId(), member.getName());
return member.getName();
}
}
MemberRepositoryTest
@SpringBootTest
public class MemberRepositoryTest {
private final MemberRepository memberRepository;
@Autowired
public MemberRepositoryTest(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Test
void saveTest(){
//given
Member member = new Member();
member.setName("nathan");
//when
String savedMemberName = memberRepository.save(member);
//then
Assertions.assertThat(savedMemberName).isEqualTo("nathan");
}
여기에서 @Autowired를 빼면 맨위에서 설명했던 org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter
에러가 발생한다.
JUnit5는 다음과 같이 이루어져 있다.
JUnit Platform
+JUnit Jupiter
+JUnit Vintage
org.junit.jupiter.api.extension
에 ParameterResolver Interface가 존재한다.어댑터 패턴으로 구현된 Parameter Resolver Interface
그것을 구현하고 있는 SpringExtension class
그것을 애노테이션으로 갖고 있는 @SpringBootTest (@ExtendWith({SpringExtension.class})
)
@SpringBootTest의 supportsParameter 메서드 (구현부)
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
Parameter parameter = parameterContext.getParameter();
Executable executable = parameter.getDeclaringExecutable();
Class<?> testClass = extensionContext.getRequiredTestClass();
PropertyProvider junitPropertyProvider = (propertyName) -> {
return (String)extensionContext.getConfigurationParameter(propertyName).orElse((Object)null);
};
return TestConstructorUtils.isAutowirableConstructor(executable, testClass, junitPropertyProvider) || ApplicationContext.class.isAssignableFrom(parameter.getType()) || this.supportsApplicationEvents(parameterContext) || ParameterResolutionDelegate.isAutowirable(parameter, parameterContext.getIndex());
}
TestConstructorUtils.isAutowirableConstructor
를 통하여 @Autowired
를 체크한 뒤 의존성 주입을 한다.
public static boolean isAutowirableConstructor(Executable executable, Class<?> testClass, @Nullable PropertyProvider fallbackPropertyProvider) {
return executable instanceof Constructor && isAutowirableConstructor((Constructor)executable, testClass, fallbackPropertyProvider);
}
이 글의 핵심!
public static boolean isAutowirableConstructor(Constructor<?> constructor, Class<?> testClass, @Nullable PropertyProvider fallbackPropertyProvider) {
if (AnnotatedElementUtils.hasAnnotation(constructor, Autowired.class)) {
return true;
} else {
AutowireMode autowireMode = null;
TestConstructor testConstructor = (TestConstructor)TestContextAnnotationUtils.findMergedAnnotation(testClass, TestConstructor.class);
if (testConstructor != null) {
autowireMode = testConstructor.autowireMode();
} else {
String value = SpringProperties.getProperty("spring.test.constructor.autowire.mode");
autowireMode = AutowireMode.from(value);
if (autowireMode == null && fallbackPropertyProvider != null) {
value = fallbackPropertyProvider.get("spring.test.constructor.autowire.mode");
autowireMode = AutowireMode.from(value);
}
}
return autowireMode == AutowireMode.ALL;
}
}
hasAnnotation
을 통하여 @Autowired
를 체크한다.
JUnit 5 동작 원리에 대해 공부중이었는 데, 잘 보고 갑니다 :)