RestAdapter.Builder
はsetClient()
できるので、テスト時はそこにテスト用のClientをセットしてあげる。そのテスト用のClientをMockClientとしました。
本当は、Clientをわざわざ作るのがめんどくさいなと思って作らない方針で行こうとしたんですが、基盤チームの人にsetClient
する方法があるでと改めて指摘されたあと考えなおして、Clientを作るのはだるいけど作ったほうが後々楽でわかりやすいなと思えてきたので作りました。
流れとしては、以下のような感じを想定しています。
@Before
でMockClientをセットする(正確にはRestAdapterをセットする)@Test
で なにをどうモックするか指定する@After
で指定したモックをクリアする
// MockClientTest.java public class MockClientTest { MockClient mMockClient = new MockClient(); @Before public void setUp() throws Exception { // RestAdapterを作ってセット ApiClient.getInstance().setRestAdapter(RestAdapterFactory.newMocking(mMockClient)); } @After public void tearDown() throws Exception { // 次のテストで引き継がないようにclear mMockClient.clear(); } @Test public void testMock() throws Exception { // ここでAPIのbodyはなにを返すか指定する mMockClient.mock("/v1/reviews/100.json").to(200, "reviews/ok.json"); Review review = ApiClient.getInstance().create(ReviewsService.class).fetch(100); assertThat(review.getStar()).isEqualTo(5); } @Test public void testMock_route() throws Exception { MockClient.Route route = mMockClient.mock("/v1/reviews/100.json").to(200, "reviews/ok.json"); ApiClient.getInstance().create(ReviewsService.class).fetch(100); assertThat(route.called()).isEqualTo(true); // mockは呼ばれたか? assertThat(route.times()).isEqualTo(1); // 呼ばれた回数 } @Test public void testMock_regex() throws Exception { // 正規表現でも可能 MockClient.Route route = mMockClient.mock("/v1/reviews/\\d.json").to(200, "reviews/ok.json"); ApiClient.getInstance().create(ReviewsService.class).fetch(100); assertThat(route.called()).isEqualTo(true); } }
// MockClient.java public class MockClient implements Client { List<Route> mRoutes = new ArrayList<>(); Route notFoundRoute = new Route("404").to(404, "response/not_found.json"); @Override public Response execute(Request request) throws IOException { for (Route route : mRoutes) { Response response = route.match(request.getUrl()); if (response == null) { continue; } return response; } return notFoundRoute.newResponse(request.getUrl()); } public Route mock(String urlPath) { Route route = new Route(urlPath); mRoutes.add(route); return route; } public void clear() { mRoutes = new ArrayList<>(); } public class Route { Pattern mUrlPathRegex; int mStatus; int mTimes; String mJsonPath; Route(String urlPath) { mUrlPathRegex = Pattern.compile(urlPath); } public Route to(int status, String jsonPath) { mStatus = status; mJsonPath = jsonPath; return this; } public int times() { return mTimes; } public boolean called() { return mTimes >= 1; } private Response match(String url) throws IOException { Matcher matcher = mUrlPathRegex.matcher(url); if (!matcher.find()) { return null; } return newResponse(url); } private String getContentType() { return "application/json"; } private Response newResponse(String url) throws IOException { // https://github.com/gfx/Android-Helium/blob/master/app/src/test/java/com/github/gfx/helium/TestUtils.java#L22 byte[] body = TestUtils.getAssetFileInBytes(mJsonPath); mTimes++; return new Response( url, mStatus, "mocked-response", new ArrayList<Header>(), new TypedByteArray(getContentType(), body) ); } } }
テストが並列で走ることとかはまだ考えてないです。
Clientを作るのめんどいと思ったけど、案外すぐ出来たので、そういうの良くないなって思いました。