今回は、SpringのRestTemplate
を使って外部のWeb API(REST API)にアクセスするようなコンポーネントに対するJUnitテストケースの書き方を紹介します。単体テストでは、Mockitoなどのモック化フレームワークを使って依存コンポーネントの振る舞いを変えることも多いと思いますが、今回は、Spring Testが提供しているMockRestServiceServer
を使います。
動作検証環境
- Spring Framework 4.3.0.BUILD-SNAPSHOT (2016/6/4時点)
- Spring Boot 1.4.0.BUILD-SNAPSHOT (2016/6/4時点)
RestTemplate
って何者!?
MockRestServiceServer
が何者か説明する前に、RestTemplate
が何者でどんな仕組みでWeb APIにアクセスしているか説明しておきましょう。
RestTemplate
は、ざっくり言えばSpringが提供しているHTTPクライアント用のクラスです。少しだけ正確にいうと・・・・単純なHTTPクライアントより上位のレイヤに位置付けられ「JavaオブジェクトとHTTPボディの変換」「エラー時の例外変換」といったHTTPの通信処理には直接関係ないけど、あると便利な機能が盛り込まれています。そしてRestTemplate
の大きな特徴としては、実際のHTTP通信処理をJava SEの標準クラス(java.net.HttpURLConnection
)や3rdパーティ製のHTTPクライアントライブラリ(Apache Http Components, OkHttp, Nettyなど)を利用して行う仕組みになっているところでしょう。そして、通信レイヤに使うライブラリを意識する実装は不要です (もちろんRestTemplate
をコンフィギュレーションする時にはライブラリを意識する必要はありますけどね )
RestTemplate
を中心としたSpringのHTTPクライアントのアーキテクチャは、だいたい以下のようなイメージです。
No | 説明 |
---|---|
① | アプリケーション(ここでは@Repository クラス)がRestTemplate のメソッドを呼び出し、外部APIへのアクセスを依頼する |
② |
RestTemplate は、HttpMessageConverter を使用して、Javaオブジェクト(JavaBeanなど)をJSONなどに変換する。 |
③ |
RestTemplate は、HttpClientRequestFactory 経由で外部APIへのHTTP通信を依頼する。 |
④ |
HttpClientRequestFactory の実装クラス(デフォルトでは、Java SEのクラスを利用してHTTP通信を行うSimpleClientHttpRequestFactory )は、外部APIへHTTP通信を行う。 |
⑤ |
RestTemplate は、HttpMessageConverter を使用して、外部APIから応答されたJSONなどをJavaオブジェクト(JavaBeanなど)に変換する。 |
MockRestServiceServer
って何者!?
で、MockRestServiceServer
を使うと、RestTemplate
の振る舞いを以下のように変更することができます。
HttpClientRequestFactory
の実装クラスが、テストケース側から指定したモックレスポンスを返却するクラス(MockClientHttpRequestFactory
)に差し代わり、実際の外部APIを呼び出す代わりにモックレスポンスを応答してくれるわけです。こうすることで、HTTP通信部分を除いたRestTemplate
との連携部分(HttpMessageConverter
、エラーハンドリング部品、共通処理を挟み込む部品など)を適用した状態でテストできます この仕組みは、単体テスト観点のモックではなく、モジュール結合テスト観点時につかうモックという印象です(単体テスト観点でも使えますけどね)。
RestTemplate
を使うコンポーネントを作る
前置きが長くなってしまいましたが、実際にテストをしてみましょう!!テストするためには・・・テスト対象のクラスを作る必要です
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.web.client.RestOperations;
@Repository
public class TodoRepository {
@Autowired
RestOperations restOperations;
public Todo findOne(String todoId) {
return restOperations.getForObject("https://api.domain/todos/{todoId}", Todo.class, todoId);
}
}
RestTemplate
のBean定義も行う必要があります。
@Configuration
public class ExternalApiConfig {
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
RestTemplate
を使うコンポーネントをテストする
本来なら、Spring MVC → @Controller
→ @Service
→ @Repository
→ RestTemplate
といった感じでコンポーネント結合した状態でテストする方がよいですが、ここでは、@Repository
→ RestTemplate
の部分だけ結合した状態でテストします。
@Autowired
RestTemplate restTemplate; // テスト対象のクラスで使用するRestTemplateと同じものをテストケースクラスにもインジェクションする
@Autowired
TodoRepository todoRepository; // テスト対象のBeanもインジェクションする
@Test
public void findOne() {
// テスト対象のクラスで使用するRestTemplateにMockRestServiceServerを割り当てる
// MockHttpClientRequestFactoryに差し替わる
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
// テスト対象のクラスでアクセスリクエストに対して、モックレスポンスを設定する
// ここでは、
// ・ HTTPレスポンスコード : 200 OK
// ・ HTTPレスポンスボディ : JSON
// をモックレスポンスに設定している。
mockServer.expect(requestTo("https://api.domain/todos/123"))
.andRespond(withSuccess("{\"id\":\"123\", \"title\":\"タイトル\", \"finished\":false}", MediaType.APPLICATION_JSON_UTF8));
// テスト実施
Todo todo = todoRepository.findOne("123");
// モックレスポンスを元に生成したTodoオブジェクトの検証する
assertThat(todo.getId(), is("123"));
assertThat(todo.getTitle(), is("タイトル"));
assertThat(todo.isFinished(), is(false));
// セットアップしたモックレスポンスが全て消費されたか検証する
mockServer.verify();
}
どうでしょうか?いい感じですよね
ここでは正常系(200 OK)のテストケースですが、クライアントエラー(4xx系)やサーバーエラー(5xx系)のテストも簡単にできちゃいます!!
MockRestServiceServer
を紐解く
MockRestServiceServer
の基本的な使い方は、↑で紹介した通りです。が、しかし、当然ながらいろいろな機能があるので、そちらも簡単に紹介しておきましょう。
複数リクエストに対応するには?
ひとつの処理で複数のAPI呼び出し(リクエスト)を行うこともあると思いますが、その場合はどうすればよいのでしょうか?テスト内で複数のリクエストに対応する場合は、テストシナリオにあわせてexpect
メソッドを呼び出してモックレスポンスを用意するだけです。
// ...
mockServer.expect(requestTo("https://api.domain/todos/123"))
.andRespond(withSuccess("{\"id\":\"123\", \"title\":\"タイトル\", \"finished\":false}", MediaType.APPLICATION_JSON_UTF8));
mockServer.expect(requestTo("https://api.domain/todos/124"))
.andRespond(withSuccess("{\"id\":\"124\", \"title\":\"タイトル\", \"finished\":true}", MediaType.APPLICATION_JSON_UTF8));
// ...
「Spring 4.3 テスト関連の主な変更点」で紹介しましたが、Spring 4.3から、指定したモックレスポンスを返却する回数を指定できるようになります。また、デフォルトではexpect
を呼び出した順番でモックレスポンスが消費されますが、順番を無視するオプションも用意されます。
リクエストを特定する方法はURLだけ?
もちろんURL以外の値をつかってリクエストを特定できます。
- HTTPメソッド
- リクエストヘッダ
- リクエストボディ
リクエストの特定は、expect
とandExpect
メソッドをつかって絞り込みます。これらのメソッドにはRequestMatcher
のオブジェクトを指定するのですが、Built-inで様々なRequestMatcher
が用意されています。Built-inで用意されているRequestMatcher
については、MockRestRequestMatchersのソースコードみてください。JSONPathやXPathにも対応しています
モックレスポンスのバリエーションは?
サンプルではHTTPレスポンスに「200 OK」、HTTPレスポンスボディに「JSON形式の文字列」を指定していますが、どのようなバリエーションがあるのでしょうか?モックレスポンスは、andRespond
メソッドをつかって指定します。andRespond
メソッドにはResponseCreator
のオブジェクトを指定するのですが、Built-inでいくつかのResponseCreator
が用意されています。Built-inで用意されているResponseCreator
については、MockRestResponseCreatorsのソースコードみてください。
まとめ
RestTemplate
を使うアプリのテストを行う上で、MockRestServiceServer
は必須といっても過言ではない気がします。もちろんMockitoなどのフレームワークを利用する方法もありますが、個人的にはMockRestServiceServer
を使うことを強くお勧めします