오류가 발생한 코드
@RestController
@RequestMapping("/api/v1")
public class ForecastController {
private final ForecastYmlRead forecastYmlRead;
private final RestTemplate restTemplate;
private final ForecastService forecastService;
public ForecastController(RestTemplateBuilder restTemplateBuilder, ForecastService forecastService, ForecastYmlRead forecastYmlRead){
this.restTemplate = restTemplateBuilder.build();
this.forecastService = forecastService;
this.forecastYmlRead = forecastYmlRead;
}
//테스트할 메소드 구현
}
@RunWith(SpringRunner.class)
@WebMvcTest(ForecastController.class)
public class ForecastControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void get_request_dateTime_in_api() throws Exception {
mockMvc.perform(get("/api/v1/forecast"))
.andExpect(status().isOk())
.andDo(print());
}
}
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'org.springframework.boot.web.client.RestTemplateBuilder' available
컨트롤러의 메소드를 테스트하고자 @WebMvcTest로 테스트를 수행하였지만
해당 컨트롤러를 생성하는 데 필요한 빈을 정의하지 못해 발생한 오류이다.
오류의 원인
해당 오류는 RestTemplateBuilder만을 지목하고 있지만, 사실 ForecastController 생성에 필요한
ForecastYmlRead, ForecastService 또한 주입받지 못해 오류가 발생한다.
이 오류는 @WebMvcTest의 특성 때문에 발생한다.
@WebMvcTest는 슬라이스 테스트로, 보통 컨트롤러 하나만 테스트하고 싶을 때 사용한다.
웹과 관련된 빈만 주입되며 (@Controller, @ControllerAdvice, @JsonComponent, @Converter)
@Service와 같은 일반적인 @Component는 생성되지 않는다.
따라서 해당 컨트롤러 생성에 필요한 3종류의 빈은 주입되지 않으므로, 컨트롤러를 생성조차 할 수 없다.
오류 해결!
@RunWith(SpringRunner.class)
@WebMvcTest(ForecastController.class)
public class ForecastControllerTest {
@Autowired
MockMvc mockMvc;
@MockBean
RestTemplateBuilder restTemplateBuilder;
@MockBean
ForecastService forecastService;
@MockBean
ForecastYmlRead forecastYmlRead;
@Test
public void get_request_dateTime_in_api() throws Exception {
mockMvc.perform(get("/api/v1/forecast"))
.andExpect(status().isOk())
.andDo(print());
}
}
결국 Web Layer 아래 의존성이 모두 제거되기 때문에 필요 의존성은 @MockBean으로 채워야 한다.
대신 @MockBean으로 주입받은 인스턴스의 경우 필요한 동작은 직접 구현해줘야 한다 (껍데기만 받아왔기 때문)
@AutoConfigureWebClient
@RunWith(SpringRunner.class)
@WebMvcTest(ForecastController.class)
@AutoConfigureWebClient
public class ForecastControllerTest {
@Autowired
MockMvc mockMvc;
@MockBean
ForecastService forecastService;
@MockBean
ForecastYmlRead forecastYmlRead;
@Test
public void get_request_dateTime_in_api() throws Exception {
mockMvc.perform(get("/api/v1/forecast"))
.andExpect(status().isOk())
.andDo(print());
}
}
그리고 구글링을 하다보면 RestTemplateBuilder의 경우 @AutoConfigureWebClient를 사용해도 주입받을 수 있다고 한다.
이는 @AutoConfigureWebClient가 RestTemplate 및 RestTemplateBuilder를 빈으로 등록해주기 때문이다.
MockBean의 동작을 직접 구현하지 않고, 바로 RestTemplate을 사용하고 싶다면 이 또한 좋은 방법이다.
//AutoConfigureWebClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration
@PropertyMapping("spring.test.webclient")
public @interface AutoConfigureWebClient {
/**
* If a {@link RestTemplate} bean should be registered. Defaults to {@code false} with
* the assumption that the {@link RestTemplateBuilder} will be used.
* @return if a {@link RestTemplate} bean should be added.
*/
boolean registerRestTemplate() default false;
}
//RestTemplateBuilder를 빈으로 등록한다.
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(HttpMessageConvertersAutoConfiguration.class)
@ConditionalOnClass(RestTemplate.class)
@Conditional(NotReactiveWebApplicationCondition.class)
public class RestTemplateAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder(ObjectProvider<HttpMessageConverters> messageConverters,
ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers,
ObjectProvider<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
RestTemplateBuilder builder = new RestTemplateBuilder();
HttpMessageConverters converters = messageConverters.getIfUnique();
if (converters != null) {
builder = builder.messageConverters(converters.getConverters());
}
builder = addCustomizers(builder, restTemplateCustomizers, RestTemplateBuilder::customizers);
builder = addCustomizers(builder, restTemplateRequestCustomizers, RestTemplateBuilder::requestCustomizers);
return builder;
}
...
}
//AutoConfigureWebClient를 구현하며 RestTemplate을 빈으로 등록한다.
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.test.webclient", name = "register-rest-template")
@AutoConfigureAfter(RestTemplateAutoConfiguration.class)
public class WebClientRestTemplateAutoConfiguration {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
'학습 > Spring' 카테고리의 다른 글
[spring] @Controller vs @RestController (0) | 2021.05.25 |
---|---|
[spring] Spring State Machine (0) | 2020.10.31 |
[spring] 스프링 부트에서 REST Client 이용하기 (0) | 2020.05.23 |
[spring] 스프링 부트에서 Spring Security 커스터마이징하기 (0) | 2020.05.21 |
[spring] 스프링 부트에서 Spring Security 사용하기 (0) | 2020.05.19 |
댓글