7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RestTemplateで外部のWeb APIにアクセスするクラスのテストを、MockRestServiceServerを使って書く

Last updated at Posted at 2023-07-26

やりたいこと

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、ボディなし

テスト対象

HelloClient.java
@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";
    }
}
HelloRequest.java
public record HelloRequest(String message) {
}
HelloResponse.java
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要素で適当な値を指定します(本当に適当で大丈夫そうです)。

HelloClientTest.java(一部)
@RestClientTest(
        components = HelloClient.class,
        properties = "hello-service.base-url=http://localhost"
)
class HelloClientTest {

    @Autowired
    HelloClient helloClient;

    @Autowired
    MockRestServiceServer server;
    ...
}

GETリクエストに対するテスト

HelloClientTest.java(一部)
    @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リクエストに対するテスト

HelloClientTest.java(一部)
    @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")));
        }
    }

参考資料

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?