본문 바로가기
학습/Spring

[spring] 스프링 부트에서 @WebMvcTest 사용하며 RestTemplate 주입받기

by KKambi 2020. 6. 11.

 

오류가 발생한 코드

@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();
	}
}

 

댓글