AndroidのUnitTest用モックサーバーをYAMLとJSONから作る

## 背景

- [ニフティクラウド mobile backend](http://mb.cloud.nifty.com)のSDKが4つ
 - iOS / Android / JavaScript / Unity
- JavaScriptのUnit Testで[Stubcell](https://github.com/yosuke-furukawa/stubcell)を使ったスタブサーバーを用意していた
- その時のYAMLファイルとJSONファイルを使いまわしてAndroidでもテストしたかった

## 準備

- AndroidのUnit Test環境の構築は以下を参照
 - [MockWebServerを使ってAndroidアプリのHTTP通信テストをしてみる](http://rebirthble.github.io/2015/05/12/using-mock-web-server.html)
 - assetsフォルダにあるレスポンスのJSONファイルを読み込むところまではできていた

## YAMLを使うライブラリの設定

- [snakeyaml](https://bitbucket.org/asomov/snakeyaml)を使う

### build.gradleを編集

- mavenCentralをリポジトリに追加 

```
repositories {
    jcenter()
    mavenCentral()
}
```

- dependenciesにsnakeyamlを追加

```
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.0.0'
    testCompile 'junit:junit:4.12'
    testCompile 'org.robolectric:robolectric:2.4'
    testCompile 'com.squareup.okhttp:mockwebserver:2.3.0'
    compile 'com.google.android.gms:play-services:7.5.0'
    testCompile 'org.yaml:snakeyaml:1.15'
}
```

## Dispatch部分を編集

- MockWebServerではDispatchを作成してHTTPリクエストのハンドリングが可能
- Readmeではif / elseでの条件分岐を行っているけど、ようは条件よってMockResponseのreturnができればよい
- YAMLファイルを読み込んで、引数のrequestと一致するものを探す実装
- Dispatchを使ったMockWebServerの立て方や、readJsonResponseメソッド(レスポンス用のJSONデータをファイルから読み込むところ)は以下参照
 - [MockWebServerを使ってAndroidアプリのHTTP通信テストをしてみる](http://rebirthble.github.io/2015/05/12/using-mock-web-server.html)
- YAMLファイルでのリクエスト/レスポンスの書き方も[Stubcell](https://github.com/yosuke-furukawa/stubcell)のReadmeを参照


```java
//URLごとにレスポンスを定義するdispatcherを作成
final Dispatcher dispatcher = new Dispatcher() {

    @Override
    public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
        InputStream input = null;
        try {
            input = new FileInputStream(new File("src/test/assets/yaml/test.yml"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        //YAMLオブジェクトを作成
        Yaml yaml = new Yaml();

        Map<String, Object>map = null;
        Map<String, Object>requestMap = null;
        Map<String, Object>responseMap = null;

        //YAMLファイルから---で区切られたデータを取得
        for (Object data : yaml.loadAll(input)) {
            map = (Map<String, Object>)data;
            requestMap = (Map)map.get("request");
            responseMap = (Map)map.get("response");
            if (requestMap.get("url").equals(request.getPath())){
                if (requestMap.get("method").equals(request.getMethod())){
                    //Responseを返す
                    return new MockResponse().setResponseCode((int)responseMap.get("status"))
                                             .setHeader("Content-Type", "application/json")
                                             .setBody(readJsonResponse(responseMap.get("file").toString()));
                }
            }
        }

        //デフォルトのレスポンスを設定
        return new MockResponse().setResponseCode(404)
                                 .setHeader("Content-Type", "application/json")
                                 .setBody(readJsonResponse("valid_error_response.json"));

    }
};
```

- YAMLファイルサンプル

```
---
  request:
    url: /2013-09-01/classes/TestClass
    method: POST
  response:
    status: 201
    file: valid_post_response.json
---
  request:
    url: /2013-09-01/classes/TestClass/7FrmPTBKSNtVjajm
    method: PUT
  response:
    status: 200
    file: valid_put_response.json
---
  request:
    url: /2013-09-01/classes/TestClass/7FrmPTBKSNtVjajm
    method: GET
  response:
    status: 200
    file: valid_get_response.json
---
  request:
    url: /2013-09-01/classes/TestClass/7FrmPTBKSNtVjajm
    method: DELETE
  response:
    status: 200
    file: valid_delete_response.json

```

## さいごに

- (まだ)urlとメソッドでしかハンドリングに対応していません
 - request bodyとかquer stringとか。。。
- (まだ)url parserには対応してません(placeholder /user/:userId みたいなやつ)
- iOSでも同じことをやれば、YAMLとJSONファイルがある意味テストの仕様書になる
- テストコードもすっきりするはず