RestClient는 Spring 6.1 버전부터 추가된 동기식 HTTP 클라이언트로 기존의 동기식 HTTP 클라이언트인 RestTemplate에 비해 훨씬 더 현대적인 API를 제공합니다.
RestClient의 테스트 방법을 찾던 중 유효하게 사용할 수 있는 두 가지 방법에 대해 알게 되어 기록합니다.
@RestClientTest
@RestClientTest를 사용하면 Jackson, GSON 등의 클라이언트 테스트와 관련된 bean 구성만 적용하여 최소한의 컨텍스트로 테스트가 실행됩니다.
이 때, 테스트를 위해 사용하는 MockRestServiceServer와 RestClient가 바인드되어야 하기 때문에 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를 사용하지 않는다면 MockRestServiceServer와 RestClient가 바인딩되지 않아 사용할 수 없다면서 예외가 발생하게 됩니다.

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()에 호출할 메서드를 모킹하는 것과 비슷합니다.andRespondexpect()에서 지정한 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를 이용합니다. MockResponse는 setResponseCode(), 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
'Spring' 카테고리의 다른 글
| 스프링 @Retry를 이용한 재시도 처리하기 (0) | 2024.06.12 |
|---|---|
| 스프링 @Async를 이용한 비동기 실행 적용하기 (1) | 2024.06.11 |
| 스프링 이벤트(Spring Event) 사용해보기 (0) | 2024.02.12 |
| 로그 세팅하기 (1) | 2023.11.19 |
| @WebMvcTest와 테스트 코드 개선하기 (0) | 2023.10.04 |