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()
에 호출할 메서드를 모킹하는 것과 비슷합니다.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
를 이용합니다. 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 |