SpringのRestTemplateを使うコンポーネントのJUnitテストはこう書く！！

今回は、Springの`RestTemplate`を使って外部のWeb API(REST API)にアクセスするようなコンポーネントに対するJUnitテストケースの書き方を紹介します。単体テストでは、[Mockito](http://mockito.org/)などのモック化フレームワークを使って依存コンポーネントの振る舞いを変えることも多いと思いますが、今回は、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など)を利用して行う仕組みになっているところでしょう。そして、通信レイヤに使うライブラリを意識する実装は不要です :+1: (もちろん`RestTemplate`をコンフィギュレーションする時にはライブラリを意識する必要はありますけどね :sweat_smile: )

`RestTemplate`を中心としたSpringのHTTPクライアントのアーキテクチャは、だいたい以下のようなイメージです。

![spring-resttemplate.png](https://qiita-image-store.s3.amazonaws.com/0/117313/a7d14dba-6f6e-2b25-5e01-ae34fd1cadce.png)

| 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`の振る舞いを以下のように変更することができます。

![spring-mockrequestserviceserver.png](https://qiita-image-store.s3.amazonaws.com/0/117313/05719114-7c52-9803-60bc-d42481c69bfa.png)

`HttpClientRequestFactory`の実装クラスが、テストケース側から指定したモックレスポンスを返却するクラス(`MockClientHttpRequestFactory`)に差し代わり、実際の外部APIを呼び出す代わりにモックレスポンスを応答してくれるわけです。こうすることで、HTTP通信部分を除いた`RestTemplate`との連携部分(`HttpMessageConverter`、エラーハンドリング部品、共通処理を挟み込む部品など)を適用した状態でテストできます :+1: この仕組みは、単体テスト観点のモックではなく、モジュール結合テスト観点時につかうモックという印象です（単体テスト観点でも使えますけどね）。


# `RestTemplate`を使うコンポーネントを作る

前置きが長くなってしまいましたが、実際にテストをしてみましょう！！テストするためには・・・テスト対象のクラスを作る必要です :wink: 

```java
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定義も行う必要があります。

```java
@Configuration
public class ExternalApiConfig {
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
```

# `RestTemplate`を使うコンポーネントをテストする

本来なら、Spring MVC → `@Controller` → `@Service` → `@Repository` → `RestTemplate` といった感じでコンポーネント結合した状態でテストする方がよいですが、ここでは、`@Repository` → `RestTemplate`の部分だけ結合した状態でテストします。

```java
@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();

}
```

どうでしょうか？いい感じですよね :laughing: 
ここでは正常系(200 OK)のテストケースですが、クライアントエラー(4xx系)やサーバーエラー(5xx系)のテストも簡単にできちゃいます！！

# `MockRestServiceServer`を紐解く

`MockRestServiceServer`の基本的な使い方は、↑で紹介した通りです。が、しかし、当然ながらいろいろな機能があるので、そちらも簡単に紹介しておきましょう。

## 複数リクエストに対応するには？

ひとつの処理で複数のAPI呼び出し（リクエスト）を行うこともあると思いますが、その場合はどうすればよいのでしょうか？テスト内で複数のリクエストに対応する場合は、テストシナリオにあわせて`expect`メソッドを呼び出してモックレスポンスを用意するだけです。

```java
// ...
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 テスト関連の主な変更点](http://qiita.com/kazuki43zoo/items/6b6ecafd8e69a54e4803#%E3%83%A2%E3%83%83%E3%82%AF%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%82%92%E8%BF%94%E5%8D%B4%E3%81%99%E3%82%8B%E5%9B%9E%E6%95%B0%E3%81%AE%E5%88%B6%E5%BE%A1)」で紹介しましたが、Spring 4.3から、指定したモックレスポンスを返却する回数を指定できるようになります。また、デフォルトでは`expect`を呼び出した順番でモックレスポンスが消費されますが、順番を無視するオプションも用意されます。

## リクエストを特定する方法はURLだけ？

もちろんURL以外の値をつかってリクエストを特定できます。

* HTTPメソッド
* リクエストヘッダ
* リクエストボディ

リクエストの特定は、`expect`と`andExpect`メソッドをつかって絞り込みます。これらのメソッドには`RequestMatcher`のオブジェクトを指定するのですが、Built-inで様々な`RequestMatcher`が用意されています。Built-inで用意されている`RequestMatcher`については、[MockRestRequestMatchers](https://github.com/spring-projects/spring-framework/blob/master/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java)のソースコードみてください。JSONPathやXPathにも対応しています :+1: 


## モックレスポンスのバリエーションは？

サンプルではHTTPレスポンスに「200 OK」、HTTPレスポンスボディに「JSON形式の文字列」を指定していますが、どのようなバリエーションがあるのでしょうか？モックレスポンスは、`andRespond`メソッドをつかって指定します。`andRespond`メソッドには`ResponseCreator`のオブジェクトを指定するのですが、Built-inでいくつかの`ResponseCreator`が用意されています。Built-inで用意されている`ResponseCreator`については、[MockRestResponseCreators](https://github.com/spring-projects/spring-framework/blob/master/spring-test/src/main/java/org/springframework/test/web/client/response/MockRestResponseCreators.java)のソースコードみてください。


# まとめ

`RestTemplate`を使うアプリのテストを行う上で、`MockRestServiceServer`は必須といっても過言ではない気がします。もちろんMockitoなどのフレームワークを利用する方法もありますが、個人的には`MockRestServiceServer`を使うことを強くお勧めします :blush: 

# 参考サイト

* http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#spring-mvc-test-client
* http://terasolunaorg.github.io/guideline/5.1.0.RELEASE/ja/ArchitectureInDetail/RestClient.html