한가로운 어느 일요일, 저는 평소와 같이 열심히 테스트 코드를 작성하고 있었습니다. 테스트 코드를 작성하던 중, 언젠가 테스트 코드에서는 의존성 주입이 안된다는 사실을 알고 있냐는 크루의 물음이 떠올랐습니다. 그동안 편의를 위해 Autowired를 마구 쓰던 저는 이번 기회에 한번 테스트해보자는 마인드로 생성자 주입을 하도록 코드를 변경해 보았습니다.
그 결과…
@SpringBootTest
public class ProductServiceTest {
private final ProductService productService;
public ProductServiceTest(ProductService productService) {
this.productService = productService;
}
...
역시 안되는군요.. 크루는 거짓말을 하지 않았습니다. 그렇다면 왜 되지 않는 걸까요? @SpringBootTest를 써 주었으니 문제가 없는게 아닐까요? 대충 에러 메세지를 보아 하니.. ProductService라는 파라메터에 대한 ParameterResolver가 등록되지 않았다고 뜨는군요. 이 부분을 한번 구글의 힘을 빌려 찾아 보겠습니다.
해결법 자체는 생성자에 있는 파라메터를 제거하고 @BeforeEach에서 초기화를 해주거나 @Autowired로 필드를 초기화한다고 나오는 거 같습니다. 해결법은 알겠고.. 원인은 무엇일까요?
무엇이 문제였을까?
@SpringBootTest를 붙이게 되면 Junit Jupiter에 SpringExtension이 자동으로 등록되어 사용됩니다. 조금 소스를 까보면서 해결책이 있을지 찾아보겠습니다.
SpringExtension.java 코드를 뒤적이다 보니 문구를 발견했습니다.
* <p><strong>WARNING</strong>: If a test class {@code Constructor} is annotated
* with {@code @Autowired} or automatically autowirable (see {@link TestConstructor}),
* Spring will assume the responsibility for resolving all parameters in the
* constructor. Consequently, no other registered {@link ParameterResolver}
* will be able to resolve parameters.
읽어보니 @Autowired가 달린 Constructor나 TestConstruct 어노테이션을 사용한 경우에 대해서만 생성자 내의 모든 파라메터를 처리한다고 하네요. 추측하건데 프로덕션 코드에서는 Autowired 어노테이션을 생략 가능하지만, SpringExtension에서는 Autowired 어노테이션이 생략 불가능해서 위와 같은 에러가 떴던 거 같습니다.
제 생각으로는 아마, 테스트 클래스를 관리하는 주체가 Spring이 아닌 Junit5이기 때문에 Junit의 방식을 따라야 하는데, Junit에서는 빈을 주입하기 위해서는 Junit에서 제공하는 ParameterResolver를 통해 주입해야 하기 때문인 것 같습니다. Junit의 파라메터 중 어떤 파라메터를 스프링의 ApplicationContext에서 주입받아야 하는지 구분하기 위해 Autowired 어노테이션에 의존하게 된 것 같습니다.
https://www.baeldung.com/junit-5-parameters
결론
결론적으로, Autowired는 필수적으로 써주어야 합니다. 주석 내에 @TestConstruct라는 어노테이션이 있어 이를 사용해 보겠습니다. @TestConstruct는 생성자 주입을 어떻게 처리할 지 결정한다고 보는게 가장 좋을 것 같습니다. ALL 옵션인 경우, 모든 생성자에 Autowired가 달려있는 것으로 간주하며 생성자 주입을 시켜 줍니다. ANNOTATED 옵션인 경우 관련 어노테이션(@Autowired, @Value, …)이 붙어있는 경우에만 생성자 주입이 동작합니다.
@SpringBootTest
@TestConstructor(autowireMode = AutowireMode.ALL)
public class ProductServiceTest {
private final ProductService productService;
public ProductServiceTest(ProductService productService) {
this.productService = productService;
}
'프레임워크 > Spring' 카테고리의 다른 글
이미지 파일 업로드 도입기 (0) | 2022.10.29 |
---|---|
스프링의 생성자 주입 얕게 알아보기 (3) | 2022.10.25 |
RestTemplate를 사용한 슬랙봇으로 메세지 보내보기 (0) | 2022.10.20 |
좌충우돌 LogBack 적용기 (0) | 2022.10.18 |
AOP, Dynamic Proxy와 ProxyFactoryBean (0) | 2022.10.15 |