やりたいこと
SpringのRestTemplate
で外部のWeb APIにアクセスするクラスのテストを書きたいです。しかしその場合、アクセス先のサーバーをどうやって用意するかが問題になります。
WireMockなど、モックサーバーを用意するためのライブラリも存在します(個人的には使ったことないので詳しくは分かりません)。
今回はSpringで用意されているMockRestServiceServer
を使ってみます。MockRestServiceServer
は名前の通りモックサーバー機能を持つクラスです。RestTemplate
と連携することができます。
環境
- JDK 17
- Spring Boot 3.1.2
将来のバージョンアップにより、内容が正しくなくなる可能性もあるので注意してください。
サンプルコード
Web APIの仕様
GET /api/hello
ステータスコード200、レスポンスボディは下記
{"message": "hello"}
POST /api/hello
リクエストボディは下記
{"message": "hello"}
レスポンスはステータスコード200、ボディなし
テスト対象
@Component
public class HelloClient {
private final RestTemplate restTemplate;
public HelloClient(RestTemplateBuilder restTemplateBuilder, @Value("${hello-service.base-url}") String baseUrl) {
this.restTemplate = restTemplateBuilder.rootUri(baseUrl).build();
}
public HelloResponse getHello() {
ResponseEntity<HelloResponse> responseEntity = restTemplate.getForEntity("/api/hello", HelloResponse.class);
if (responseEntity.getStatusCode().isError()) {
throw new RuntimeException("error");
}
return responseEntity.getBody();
}
public String postHello(HelloRequest request) {
ResponseEntity<Void> responseEntity = restTemplate.postForEntity("/api/hello", request, Void.class);
if (responseEntity.getStatusCode().isError()) {
throw new RuntimeException("error");
}
return "OK";
}
}
public record HelloRequest(String message) {
}
public record HelloResponse(String message) {
}
テストコード
テストクラスに@RestClientTest
を付加します。これによりMockRestServiceServer
がBean定義済み、かつRestTemplate
と連携済み(具体的にはMockRestServiceServer#bindTo(RestTemplate)
が実行済みの状態)になります。components
要素にHelloClient.class
を指定することで、HelloClient
もBean定義済みになります。
HelloClient
のコンストラクタの第2引数に@Value("${hello-service.base-url}")
が付加されているので、properties
要素で適当な値を指定します(本当に適当で大丈夫そうです)。
@RestClientTest(
components = HelloClient.class,
properties = "hello-service.base-url=http://localhost"
)
class HelloClientTest {
@Autowired
HelloClient helloClient;
@Autowired
MockRestServiceServer server;
...
}
GETリクエストに対するテスト
@Nested
@DisplayName("getHello()")
class GetHelloTest {
@Test
@DisplayName("GETリクエストを送信してサーバーから200が返ると、レスポンスボディをHelloResponseで受け取れる")
void success() {
// モックサーバーを設定する
server.expect(requestTo("/api/hello")) // このURLに
.andExpect(method(HttpMethod.GET)) // GETリクエストすると
.andRespond(withSuccess("""
{"message":"hello"}
""", MediaType.APPLICATION_JSON)); // 200 OKとこんなJSONを返すよう設定する
// テスト実行+アサーション
HelloResponse actual = helloClient.getHello();
assertEquals("hello", actual.message());
}
@Test
@DisplayName("GETリクエストを送信してサーバーから500が返ると、RuntimeExceptionがスローされる")
void error() {
// モックサーバーを設定する
server.expect(requestTo("/api/hello")) // このURLに
.andExpect(method(HttpMethod.GET)) // GETリクエストすると
.andRespond(withServerError()); // 500が返る
// テスト実行+アサーション
assertThrows(RuntimeException.class, () -> helloClient.getHello());
}
}
POSTリクエストに対するテスト
@Nested
@DisplayName("postHello()")
class PostHelloTest {
@Test
@DisplayName("JSONをPOSTしてサーバーから200が返ると、'OK'が返る")
void success() {
server.expect(requestTo("/api/hello")) // このURLに
.andExpect(method(HttpMethod.POST)) // POSTリクエストで
.andExpect(content().json("""
{"message":"hello"}
""")) // こんなJSONを送信すると
.andRespond(withStatus(HttpStatus.OK)); // 200が返るよう設定する
// テスト実行+アサーション
String actual = helloClient.postHello(new HelloRequest("hello"));
assertEquals("OK", actual);
}
@Test
@DisplayName("JSONをPOSTしてサーバーから500が返ると、RuntimeExceptionがスローされる")
void error() {
server.expect(requestTo("/api/hello")) // このURLに
.andExpect(method(HttpMethod.POST)) // POSTリクエストで
.andExpect(content().json("""
{"message":"hello"}
""")) // こんなJSONを送信すると
.andRespond(withServerError()); // 500が返るように設定する
// テスト実行+アサーション
assertThrows(RuntimeException.class, () -> helloClient.postHello(new HelloRequest("hello")));
}
}
参考資料
MockRestServiceServer
のJavadoc- Spring Boot公式ドキュメント
-
SpringのRestTemplateを使うコンポーネントのJUnitテストはこう書く!!
- ちょっとバージョンが古いので異なる部分がありますが(
@RestClientTest
が無いなど)、MockRestServiceServer
の仕組みなどは変わらないはずです
- ちょっとバージョンが古いので異なる部分がありますが(