외부 API 호출을 위해 RestClient 사용 시 @RestClientTest
를 사용하면 편하게 서버를 모킹하여 테스트할 수 있다.
내부적으로 @AutoConfigureMockRestServiceServer
를 갖고있어 MockRestServiceServer
를 자동 구성해주기 때문이다.
@RestClientTest(RemoteVehicleDetailsService.class)
class MyRestTemplateServiceTests {
@Autowired
private RemoteVehicleDetailsService service;
@Autowired
private MockRestServiceServer server;
@Test
void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() {
this.server.expect(requestTo("/greet/details")).andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
String greeting = this.service.callRestService();
assertThat(greeting).isEqualTo("hello");
}
}
좀 특이한건 테스트 중인 bean이 RestClient.Builder
를 사용한 경우 전체 주소를 테스트 해야 된다.
server.expect(requestTo("https://example.com/greet/details"))
.andRespond(withSuccess());
그럼 서버를 사용하는 테스트에선 어떻게 해야할까?
처음엔 위에서 알게된 @AutoConfigureMockRestServiceServer
로 자동 구성해서 쓰면 될 것 같았다.
@SpringBootTest(webEnvironment = RANDOM_PORT)
@AutoConfigureMockRestServiceServer
class SomeTest {
@Autowired
private MockRestServiceServer server;
하지만 에러가 발생한다.
Unable to use auto-configured MockRestServiceServer since mock server customizers have been bound to both a RestTemplate and a RestClient
java.lang.IllegalStateException: Unable to use auto-configured MockRestServiceServer since mock server customizers have been bound to both a RestTemplate and a RestClient
난 RestTemplate
을 쓴 적이 없는데 왜 위 에러가 발생한 걸까?
현재 부트 3.2 기준으로 @SpringBootTest
가 TestRestTemplate
와 WebTestClient
bean을 등록해주기 때문이다.
- Registers a TestRestTemplate and/or WebTestClient bean for use in web tests that are using a fully running web server.
해결 방법은
@EnableAutoConfiguration(exclude = RestTemplateAutoConfiguration.class)
추가해서 제외하는 방법MockRestServiceServer
대신 MockServerRestClientCustomizer
를 주입받아 사용하는 방법이 있다이 외에도 MockRestServiceServer 말고 어떤 라이브러리 추가해서 서버 돌리는 방식을 문서에서 봤었는데 못 찾겠음ㅠ
나같은 경우 MockRestServiceServer 관련 코드를 한 곳에 모아두고 싶어서 TestConfiguration로 분리했는데,
여기에서 @AutoConfigureMockRestServiceServer
도 포함하고 있으면 좋겠다고 생각했었다.
@TestConfiguration
@AutoConfigureMockRestServiceServer
public SomeConfiguration {...
띠... 안된답니다.
그래서 보니까 @AutoConfigureMockRestServiceServer
는 활성화 되어있으면 관련 Configuration이 아래와 같이 bean으로 등록해주길래
@AutoConfiguration
@ConditionalOnProperty(prefix = "spring.test.webclient.mockrestserviceserver", name = "enabled")
public class MockRestServiceServerAutoConfiguration {
@Bean
public MockServerRestClientCustomizer mockServerRestClientCustomizer() {
return new MockServerRestClientCustomizer();
}
@Bean
public MockRestServiceServer mockRestServiceServer(MockServerRestTemplateCustomizer restTemplateCustomizer,
MockServerRestClientCustomizer restClientCustomizer) {
...
}
나도 따라서 직접 bean으로 등록해놨다.
@TestConfiguration
public class MyTestConfiguration {
@Bean
MockServerRestClientCustomizer myMockServerCustomizer() {
return new MockServerRestClientCustomizer();
}
이러면 나중에 등록된 Customizer가 자동구성에 의해 bean으로 등록될 RestClient.Builder를 얻게 되고 그걸 MockRestServiceServer에 바인딩하는 방식이었음
// RestClientAutoConfiguration.class
@Bean
RestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) {
RestClient.Builder builder = RestClient.builder() ...
return restClientBuilderConfigurer.configure(builder);
}
// MockServerRestClientCustomizer.class
@Override
public void customize(RestClient.Builder restClientBuilder) {
// ...
MockRestServiceServerBuilder serverBuilder = MockRestServiceServer.bindTo(restClientBuilder);
// ...
MockRestServiceServer server = serverBuilder.build(expectationManager);
this.servers.put(restClientBuilder, server);
솔직히 일단 해놨지만 코드를 볼 때마다 헷갈리고 이게 맞는지도 모르겠음
해당 경계의 인터페이스나 Client 인터페이스를 모킹하도록 변경했음