22
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Daggerとretrofitでサーバのレスポンスを差し替える

Posted at

サーバのモックが必要

リスト表示でデータが取って来れなかったとき,
正しくEmptyViewが表示されるかのテストを書くことになりました.

emptyview.png

そこで,Daggerretrofitを使って,サーバのレスポンスをテストコードから,
操作できるようにしました.こんな感じです.

ImageListFragmentTest.java
@Test
public void testEmptyView_show() {
    String empty_response = 
        "{\"photos\":{\"page\":1,\"pages\":25,\"perpage\":20,\"total\":500,\"photo\":[],\"stat\":\"ok\"}";
    setupMockServer(empty_response);

    // start Activity manually
    MainActivity activity = activityRule.launchActivity(new Intent());

    // check if empty view is visible
    // ...

欲しいレスポンス(empty_response)を,今回作ったsetupMockServer(empty_response)でセットすると,
そのテスト中のAPIアクセスのレスポンスはempty_responseが返るようになってます.

そのことについて書きます.

サーバとの連携部分の確認

アプリのサーバとの連携部分はretrofitを使ってるので,こんな感じです.

MyService.java
public interface MyService {

    @GET(...URL...)
    void getPopularPhotos(@Query("page") int page, Callback<ListItem> cb);

    @GET(...URL...)
    void getPhotoInfo(@Query("photo_id") String id, Callback<PhotoInfo> cb);

    //省略

}

MyServiceは,アプリのどこからでもアクセスできるよう
自作Applicationクラスにstaticで置いてます.

MyApplication.java
public class MyApplication extends Application {

    private static RestAdapter rest_adapter =
            new RestAdapter.Builder().setEndpoint(ServerURL.END_POINT).build();

    public static MyService api = rest_adapter.create(MyService.class);
    
    // 省略
}

では,Daggerを使って上のMyServiceフィールドを差し替えられるようにします.

Daggerで差し替えの仕組みづくり

Daggerでサーバ連携部分を差し替えられるように書き換えていきます.

まず,Applicationクラスを下のように書き換えて,
サーバー連携部分を外から差し込むようにします.

MyApplication.java
public class MyApplication extends Application {

    @Inject MyService api;

    // 省略

次は,差し替えで使う以下2つのModuleを作っていきます.

  1. サーバと連携するRealAPIModule (アプリ内で使う)
  2. 決められた値をレスポンスとして返すDummyAPIModule (テストで使う)

サーバと連携するRealAPIModuleは,今まで使っていたものを@Providesで返しているだけです.

RealAPIModule.java
@Module(injects = MyApplication.class, library = true)
public class RealAPIModule {
    @Provides @Singleton
    public MyService provideMyService() {
        return new RestAdapter.Builder()
                .setEndpoint(ServerURL.END_POINT)
                .build()
                .create(MyService.class);
    }
}

次は,設定した値を返すDummyAPIModuleです.

まず,指定した値を返すMockClientクラスの作成を行います.
ここでは,コンストラクタで受け取った文字列をレスポンスとして返すようしています.

MockClient.java
public class MockClient implements Client{
    private static final int HTTP_OK_STATUS = 200;
    private final String RESPONSE_JSON;

    public MockClient(String responce_json) { this.RESPONSE_JSON = responce_json; }

    @Override
    public Response execute(Request request) throws IOException {
        return createResponseWithCodeAndJson(HTTP_OK_STATUS, RESPONSE_JSON);
    }

    private Response createResponseWithCodeAndJson(int responseCode, String json) {
        return new Response("",responseCode, "nothing", Collections.EMPTY_LIST,
                new TypedByteArray("application/json", json.getBytes()));
    }
}

DummyAPIModuleはこうなります.
クライアントにMockClientを使うことで,「レスポンスをコントロール」しています.

DummyAPIModule.java
public class DummyAPIModule {
    public String mock_response;

    public DummyAPIModule(String mock_response) { this.mock_response = mock_response; }

    @Provides
    @Singleton
    public MyService provideMyService() {
        return new RestAdapter
                .Builder()
                .setEndpoint(ServerURL.END_POINT)
                .setClient(new MockClient(mock_response))  // set mock client !!
                .build()
                .create(MyService.class);
    }

}

あとは,アプリとテストでModuleを切り替えるようににして完成です.

##アプリとテストでサーバ連携部分を差し替え
方針はこうなります.

  • 基本的にはサーバにアクセスするRealAPIModuleを使う
  • テストの時だけDummyAPIModuleに切り替える.

まず,自作アプリケーションクラス MyApplication.javaを次のように書き換えます.

MyApplication.java
public class MyApplication extends Application {
    private ObjectGraph objectGraph = null;
    @Inject MyService api;

    @Override
    public void onCreate() {
        super.onCreate();
        if(objectGraph == null) {
            List modules = Collections.singletonList(new RealAPIModule());
            objectGraph = ObjectGraph.create(modules.toArray());
        }
    }

    // used to replace ObjectGraph for test
    public void setObjectGraph(ObjectGraph graph) { this.objectGraph = graph; }

    public ObjectGraph getObjectGraph() { return objectGraph; }

    public MyService api() { return api; }
}

次にベースアクティビティで,ModuleをInjectするように変えます.
これで「基本的にはサーバにアクセスする」ようになってます.

BaseActivity
public class BaseActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ((MyApplication)getApplication())
                .getObjectGraph()
                .inject(getApplication());
    }
    
    // 省略

テストでは,次のようにして
「テストの時だけDummyAPIModuleに切り替える」ようにします.

ImageListFragmentTest.java

@Rule
public ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(
        MainActivity.class,
        true,     // initialTouchMode
        false);   // launchActivity. False so we can customize the intent per test method

private void setupMockServer(String response) {
    Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
    MyApplication app =
            (MyApplication) instrumentation.getTargetContext().getApplicationContext();

    // setup objectGraph to inject Mock API
    List modules = Collections.singletonList(new DummyAPIModule(response));
    ObjectGraph graph = ObjectGraph.create(modules.toArray());
    app.setObjectGraph(graph);
    app.getObjectGraph().inject(app);
}

@Test
public void testEmptyView_show() {
    String empty_response = 
        "{\"photos\":{\"page\":1,\"pages\":25,\"perpage\":20,\"total\":500,\"photo\":[],\"stat\":\"ok\"}";
    setupMockServer(empty_response);

    // start Activity manually
    MainActivity activity = activityRule.launchActivity(new Intent());

    // check if empty view is visible
    // 省略

ActivityTestRuleのおかげで,Activityは自動でスタートしません.
そのため,DummyModuleを差し込んでから,launchActivityを呼ぶことで
差し替えが実現できます.

終わりに

もっと簡単な方法がありそうですが,Daggerを使ってみたかったのでやってみました.
合わせてDIに関していろいろ調べたので,
いいなと思ったURLをいくつか参考URLに貼ります.

ありがとうございました.

##参考URL
Dagger (公式 website)
Square Island : Dagger 2 + Espresso 2 + Mockito
Tasting Dagger 2 on Android | Fernando Cejas

22
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?