Android
test
AndroidStudio
Dagger
Retrofit

Dagger2とRetrofitを使うAndroid appをJVM unit testingしてみた

More than 3 years have passed since last update.


追記(2015/19/7)

Robolectric 3.0以降はJVM unit testingの仕組みを利用するようになりました。素の状態よりテストしやすいのでこちらを利用するとよさそうです。このエントリで例に上げたHeliumもRobolectricに移行しました。


三行まとめ


  • Android Studio 1.1からJVM unit testingがサポートされた

  • Android frameworkにあまり依存しないロジックであれば有用

  • テストファイル書き換えからテスト実行まで数秒でおわるので試行錯誤しやすい


説明

Android SDKがJVMによるテストをサポートしたという話があります:

Robolectric を使わずとも、公式のツールキットを使うだけでJVM unit testingができるというのは画期的で、今後モデルまわりのテストはこっちに行くのかなと思っています。

ただし、まだAndroid Studioで通常のandroidTestとの併用は難しいですし、テストレポートをAndroid Studioで見ることすらできません。プロダクトに取り入れる段階ではないでしょう。

またAndroid frameworkは一切使えません。Android frameworkのクラスライブラリを参照することはできるのですが、何か機能を呼ぼうとすると軒並みthrow new RuntimeException("Method foo not mocked") となります。

ともあれ、どんなものかは試しておきたいところ。というわけでテストを書いてみました。

もともとのアプリは以下のような特徴をもっています。


  • RetrofitでWeb APIにアクセスする

  • RetrofitのRxJavaインターフェイスを使用

  • Dagger 2.0 (2.0-SNAPSHOT) でDependency Injection

  • JUnit4でテストを書いている

  • Circle-CI + DockerでCI

いざテストを書いてみると、DIをしているのでmockするのは難しくありませんでした。RetrofitはHTTP clientを retrofit.client.Client というインターフェイスで抽象化しているので、mockwebserverすら必要ありません。 Client#execute() のmockを実装するだけです。

  class MockClient implements Client {

final String path;

final String assetName;

final String contentType;

MockClient(String path, String assetName, String contentType) {
this.path = path;
this.assetName = assetName;
this.contentType = contentType;
}

@Override
public Response execute(Request request) throws IOException {
URI uri = URI.create(request.getUrl());
if (uri.getPath().equals(path)) {
return resourceFoundInXml(request.getUrl());
} else {
return resourceNotFound(request.getUrl());
}
}

Response resourceNotFound(String uri) {
return new Response(uri, 404, "not found", new ArrayList<Header>(),
new TypedByteArray(contentType, new byte[]{}));
}

Response resourceFoundInXml(String uri) throws IOException {
return new Response(uri, 200, "ok", new ArrayList<Header>(),
new TypedByteArray(contentType, getAssetFileInBytes(assetName)));

}
}

@Test
public void testRequestHotentries() throws Exception {
HatebuFeedClient feedClient = new HatebuFeedClient(
new MockClient("/hatena/b/hotentry", "hotentries.rss", "application/xml"),
new MockRequestIntercepter());

List<HatebuEntry> entry = feedClient.getHotentries().toBlocking().single();
assertThat(entry, hasSize(greaterThan(0)));
}

なお、アプリ実装のClientのproviderはOkHttpを使うもので、以下のようになっています。

    @Provides

public Client provideRetrofitClient(OkHttpClient httpClient) {
return new OkClient(httpClient);
}

Android frameworkがまったく使えないので、MockContextを駆使したり

Log.d() の代わりに System.out.println() を使ったりと、通常のandroidTest以上にpure Javaよりに意識を切り替える必要はあります。モデルやAPI通信部分、ユーティリティであればかろうじてテストできるという感じでしょうか。

しかしながら、テストを実行する時間が非常に短いというのは大きなメリットで、試行錯誤しながらロジックを書いていくというケースでは今後使っていきたいという印象です。