読者です 読者をやめる 読者になる 読者になる

techium

このブログは何かに追われないと頑張れない人たちが週一更新をノルマに技術情報を発信するブログです。もし何か調査して欲しい内容がありましたら、@kobashinG or @muchiki0226 までいただけますと気が向いたら調査するかもしれません。

Retrofit2のレスポンスをmockに置き換えるMockWebServer編

ユーザー登録画面など、ネットワーク通信を伴う画面のUIテストするとき、実際のサーバへアクセスしては、テストが意図した結果にならないことがあるだろう。
MockWebServerを使用することで、サーバへのアクセスを回避し、かつ、必要なレスポンスを返すようにMockを使用することで正しく動作のテストを行える。

MockWebServerを使う準備

build.gradleに以下の記述を追加

    androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.3.0'

Testを書く

MockWebServerを使用するために、アクセス先のベースURLをMockWebServerのURLに変更する。
テストではRetrofitを使用し、Qiitaの新着投稿を取得するサービスを作成し、その結果をMockWebServerから受け取る。

MockWebServerのインスタンスを作成し、start()メソッドをコールするだけで、Mockサーバの準備は完了する。
レスポンスはテスト毎にenqueueメソッドをコールし、MockResponseをセットする。
セットしないとレスポンスは返却されず、タイムアウトとなる。
1つのテストで複数のWebアクセスを行う場合は、setDispatcher()メソッドにてDispatcherをセットし、複数のレスポンスを返却できるように実装する。

インターフェース

public interface IQiitaService {

    @GET("/api/v1/items")
    Call<ResponseBody> getNewPosts();

}

インターフェースの実体

テストのためにインスタンス取得時にベースURLを渡す実装とする

public class QiitaService {

    private IQiitaService mService;

    public static QiitaService getInstance(String url) {
        return new QiitaService(url);
    }

    private QiitaService(String url) {
        init(url);
    }

    public static final String BASE_URL = "https://qiita.com";

    private void init(String url) {
        Retrofit retrofit = new Retrofit.Builder().baseUrl(url)
                .build();
        mService = retrofit.create(IQiitaService.class);
        
    }

    public interface ResponseCallback {
        void onResponse(Response<ResponseBody> response);
    }

    public void getNewlyPost(final ResponseCallback callback) {
        Call call = mService.getNewPosts();
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                callback.onResponse(response);
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                callback.onResponse(null);
            }
        });
    }
}

テストケース

public class QiitaServiceTest extends InstrumentationTestCase {
    private MockWebServer mMockWebServer;

    public void setUp() throws Exception {
        super.setUp();
        mMockWebServer = new MockWebServer();

        mMockWebServer.start(11262);
    }

    public void tearDown() throws Exception {
        super.tearDown();
        mMockWebServer.shutdown();
    }

    private static final String RESPONSE = "[{\"id\": 1,\n" +
            " \"uuid\": \"1a43e55e7209c8f3c565\",\n" +
            " \"user\":\n" +
            "  {\"name\": \"Hiroshige Umino\",\n" +
            "   \"url_name\": \"yaotti\",\n" +
            "   \"profile_image_url\": \"https://si0.twimg.com/profile_images/2309761038/1ijg13pfs0dg84sk2y0h_normal\" },\n" +
            " \"title\": \"てすと\",\n" +
            " \"body\": \"<p>foooooooooooooooo</p>\\n\",\n" +
            " \"created_at\": \"2012-10-03 22:12:36 +0900\",\n" +
            " \"updated_at\": \"2012-10-03 22:12:36 +0900\",\n" +
            " \"created_at_in_words\": \"18 hours ago\",\n" +
            " \"updated_at_in_words\": \"18 hours ago\",\n" +
            " \"tags\":\n" +
            "  [{\"name\": \"FOOBAR\",\n" +
            "    \"url_name\": \"FOOBAR\",\n" +
            "    \"icon_url\": \"http://qiita.com/icons/thumb/missing.png\",\n" +
            "    \"versions\": ['1.2', '1.3']}],\n" +
            " \"stock_count\": 0,\n" +
            " \"stock_users\": [],\n" +
            " \"comment_count\": 0,\n" +
            " \"url\": \"http://qiita.com/items/1a43e55e7209c8f3c565\",\n" +
            " \"gist_url\": null,\n" +
            " \"tweet\": false,\n" +
            " \"private\": false,\n" +
            " \"stocked\": false\n" +
            "},\n" +
            "]\n";

    public void test_200() throws Throwable {
        mMockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(RESPONSE));

        final CountDownLatch latch = new CountDownLatch(1);
        QiitaService.getInstance(mMockWebServer.url("/").toString()).getNewlyPost(new QiitaService.ResponseCallback() {
            @Override
            public void onResponse(Response<ResponseBody> response) {
                try {
                    assertEquals(200, response.code());
                    Log.v("tag", response.body().string());

                } catch (IOException e) {
                    e.printStackTrace();
                }
                latch.countDown();
            }
        });
        latch.await();
    }

    public void test_404() throws Throwable {
        mMockWebServer.enqueue(new MockResponse().setResponseCode(404));

        final CountDownLatch latch = new CountDownLatch(1);
        QiitaService.getInstance(mMockWebServer.url("/").toString()).getNewlyPost(new QiitaService.ResponseCallback() {
            @Override
            public void onResponse(Response<ResponseBody> response) {
                assertEquals(404, response.code());
                latch.countDown();
            }
        });
        latch.await();
    }

}

Dispacherを使用する例

   mMockWebServer.setDispatcher(new Dispatcher() {
            @Override
            public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
                String path = request.getPath();
                if("/api/v1/items".equals(path)) {
                    return new MockResponse().setResponseCode(200).setBody(RESPONSE);
                } else {
                    return new MockResponse().setResponseCode(404);                    
                }

            }
        });

参照URL:
okhttp/mockwebserver at master · square/okhttp · GitHub
Qiita API documentation - Qiita:Developer
Retrofit