본문 바로가기
Spring

스프링에서 외부 API 호출을 테스트하는 방법(feat. RestClient)

by hseong 2024. 5. 20.

RestClient는 Spring 6.1 버전부터 추가된 동기식 HTTP 클라이언트로 기존의 동기식 HTTP 클라이언트인 RestTemplate에 비해 훨씬 더 현대적인 API를 제공합니다.

RestClient의 테스트 방법을 찾던 중 유효하게 사용할 수 있는 두 가지 방법에 대해 알게 되어 기록합니다.

@RestClientTest

@RestClientTest를 사용하면 Jackson, GSON 등의 클라이언트 테스트와 관련된 bean 구성만 적용하여 최소한의 컨텍스트로 테스트가 실행됩니다.

이 때, 테스트를 위해 사용하는 MockRestServiceServerRestClient가 바인드되어야 하기 때문에 RestClient를 사용하는 bean은 생성자에서 RestClient.Builder를 파라미터로 받도록 구성해야 합니다.

@Component
public RestApiClient {

    private final ObjectMapper objectMapper;
    private final RestClient restClient;

    public RestApiClient(ObjectMapper objectMapper, RestClient.Builder restClientBuilder) {  
        this.restClient = restClientBuilder.build();  
        this.objectMapper = objectMapper;  
    }

...
}

만약 bean 내부에서 별도로 RestClient를 구성해서 사용하는 등의 이유로 RestClient.Builder를 사용하지 않는다면 MockRestServiceServerRestClient가 바인딩되지 않아 사용할 수 없다면서 예외가 발생하게 됩니다.

MockRestServiceServer는 클라이언트 측에서 REST 테스트를 위해 사용하는 이름 그대로의 가짜 서버입니다.

개인적으로는 mockMvc와 생김새가 유사하다고 느꼈습니다. 차이점이 있다면 mockMvc는 응답을 검증하는 데 사용하고 mockRestServiceServer는 요청에 따른 응답을 스터빙하는데 사용한다는 점입니다.

다음은 자동 구성된 MockRestServiceServer와 테스트 대상 bean인 RestApiClient를 주입받아 실행한 테스트입니다. 앞서 설명한대로 @RestClientTest는 클라이언트 테스트와 관련된 최소한의 bean 구성만 적용하기 때문에 테스트할 value 또는 @Import를 통해 등록해주어야 합니다.

@RestClientTest(value = RestApiClient.class)
class RestApiClientAnotherTest {  

    @Autowired  
    private MockRestServiceServer mockServer;  

    @Autowired  
    private RestApiClient restApiClient;  

    @Nested  
    @DisplayName("생성하면")  
    class CreateTest {  
        @Test  
        @DisplayName("예외(apiException): 5xx 에러일 때")  
        void whenStatusCode_5xx() {  
            //given  
            String apiUrl = "http://localhost:9000/test";  
            mockServer.expect(MockRestRequestMatchers.requestTo(apiUrl))  
                .andRespond(MockRestResponseCreators.withServerError());  

            //when  
            Exception exception = catchException(  
                () -> restApiClient.post(apiUrl, Map.of(), Map.of(), ""));  

            //then  
            assertThat(exception).isInstanceOf(ApiException.class);  
        }  
    }  

}

MockRestServiceServer에서 응답을 스터빙하기 위한 주요 메서드는 다음 두 가지입니다.

  • expect
    요청에 따른 응답을 반환할 예상 url을 입력하는 메서드입니다. mockito를 사용한 단위 테스트에서 given()에 호출할 메서드를 모킹하는 것과 비슷합니다.
  • andRespond
    expect()에서 지정한 url 호출 시 반환할 응답을 지정하는 메서드입니다. body(), header(), cookies() 등 다양한 메서드를 체이닝하여 원하는 응답을 만들 수 있습니다.

테스트 실행 시 다음과 같이 상태 코드가 500인 응답이 정상적으로 반환된 것을 확인할 수 있습니다.

OkHttp

RestClient를 테스트하는 다른 방법으로는 외부 mock server 라이브러리를 이용하는 방법이 있습니다.

spring-framework 깃허브 저장소의 RestClientIntegrationTests를 살펴보면 좋은 예제를 얻을 수 있습니다. 해당 테스트는 RestClient를 테스트하기 위해 OkHttp의 MockWebServer를 사용하고 있습니다.
(참고: RestClientIntegrationTests)

MockWebServer를 사용하기 위해서 build.gradle에 다음 의존성을 추가해줍니다.

testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'

다음은 MockWebServer를 이용해 작성한 테스트입니다. 별도의 자동 구성이 없기 때문에 MockWebServer와 테스트 대상인 RestApiClient를 직접 생성합니다. 또한, MockRestServiceServer와는 달리 별도의 바인드가 필요 없기때문에 테스트 대상 bean이 RestClient.Builder를 생성자에서 파라미터로 받도록 구성하지 않아도 됩니다.

class RestApiClientTest {  

    private RestApiClient restApiClient;  

    private MockWebServer mockServer;  

    @BeforeEach  
    void setUp() throws IOException {  
        ObjectMapper objectMapper = new ObjectMapper().configure(  
            DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);  
        restApiClient = new RestApiClient(objectMapper);  

        mockServer = new MockWebServer();  
        mockServer.start();  // 서버 시작
    }  

    @AfterEach  
    void tearDown() throws IOException {  
        mockServer.shutdown();  // 서버 종료. 인스턴스가 재사용 되어서는 안 됨
    }  

    @Nested  
    @DisplayName("생성하면")  
    class CreateTest {  

        @Test  
        @DisplayName("예외(apiException): 5xx 에러일 때")  
        void whenStatusCode_5xx() {  
            //given  
            MockResponse mockResponse = new MockResponse()  
                .setResponseCode(500);  
            mockServer.enqueue(mockResponse);  
            HttpUrl baseUrl = mockServer.url("/server-error");  

            //when  
            Exception exception = catchException(  
                () -> restApiClient.post(baseUrl.toString(), Map.of(), Map.of(), ""));  

            //then  
            assertThat(exception).isInstanceOf(ApiException.class);  
        }  
    }
}

MockWebServer는 응답을 스터빙하기 위해 MockResponse를 이용합니다. MockResponsesetResponseCode(), setBody() 등과 같이 응답을 만들기 위한 다양한 메서드를 제공합니다.

  • mockServer.enqueue()
    mockResponse를 전달하면 전달한 순서대로 요청 시 응답이 반환됩니다.
  • mockServer.url()
    서버에 연결하기 위한 url을 만들고, 반환된 url을 이용하여 테스트 요청을 수행합니다.

테스트 실행 시 다음과 같이 상태 코드가 500인 응답이 정상적으로 반환된 것을 확인할 수 있습니다.

참고


https://github.com/square/okhttp/tree/master/mockwebserver
https://jojoldu.tistory.com/341
https://docs.spring.io/spring-framework/reference/testing/spring-mvc-test-client.html
https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java