square retrofit framework を使用する場合、テストのためにサーバーをモックする最良の方法は何ですか?.
考えられる方法:
新しいレトロフィット client を作成し、RestAdapter.Builder()。setClient()に設定します。これには、Requestオブジェクトを解析し、jsonをResponseオブジェクトとして返すことが含まれます。
この注釈付きインターフェイスをモッククラスとして実装し、RestAdapter.create()が提供するバージョンの代わりにそれを使用します(gsonシリアル化をテストしません)。
?
理想的には、モックされたサーバーにjson応答を提供して、gsonのシリアル化を同時にテストできるようにします。
どんな例でも大歓迎です。
次の方法1を試すことにしました
public class MockClient implements Client {
@Override
public Response execute(Request request) throws IOException {
Uri uri = Uri.parse(request.getUrl());
Log.d("MOCK SERVER", "fetching uri: " + uri.toString());
String responseString = "";
if(uri.getPath().equals("/path/of/interest")) {
responseString = "JSON STRING HERE";
} else {
responseString = "OTHER JSON RESPONSE STRING";
}
return new Response(request.getUrl(), 200, "nothing", Collections.EMPTY_LIST, new TypedByteArray("application/json", responseString.getBytes()));
}
}
そしてそれを使用して:
RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setClient(new MockClient());
それはうまく機能し、実際のサーバーに接続することなく、json文字列をテストできます!
MockClient
クラスの作成やClient
からの実装などの古いメカニズムは、Retrofit 2.0ではもう機能しないため、ここでは新しい方法を説明します。次に行う必要があるのは、以下に示すようにOkHttpClientのカスタムインターセプターを追加することです。 FakeInterceptor
クラスはintercept
メソッドをオーバーライドするだけで、アプリケーションがDEBUG
モードの場合、指定されたJSONを返します。
public final class RestClient {
private static IRestService mRestService = null;
public static IRestService getClient() {
if(mRestService == null) {
final OkHttpClient client = new OkHttpClient();
// ***YOUR CUSTOM INTERCEPTOR GOES HERE***
client.interceptors().add(new FakeInterceptor());
final Retrofit retrofit = new Retrofit.Builder()
// Using custom Jackson Converter to parse JSON
// Add dependencies:
// com.squareup.retrofit:converter-jackson:2.0.0-beta2
.addConverterFactory(JacksonConverterFactory.create())
// Endpoint
.baseUrl(IRestService.ENDPOINT)
.client(client)
.build();
mRestService = retrofit.create(IRestService.class);
}
return mRestService;
}
}
public interface IRestService {
String ENDPOINT = "http://www.vavian.com/";
@GET("/")
Call<Teacher> getTeacherById(@Query("id") final String id);
}
public class FakeInterceptor implements Interceptor {
// FAKE RESPONSES.
private final static String TEACHER_ID_1 = "{\"id\":1,\"age\":28,\"name\":\"Victor Apoyan\"}";
private final static String TEACHER_ID_2 = "{\"id\":1,\"age\":16,\"name\":\"Tovmas Apoyan\"}";
@Override
public Response intercept(Chain chain) throws IOException {
Response response = null;
if(BuildConfig.DEBUG) {
String responseString;
// Get Request URI.
final URI uri = chain.request().url().uri();
// Get Query String.
final String query = uri.getQuery();
// Parse the Query String.
final String[] parsedQuery = query.split("=");
if(parsedQuery[0].equalsIgnoreCase("id") && parsedQuery[1].equalsIgnoreCase("1")) {
responseString = TEACHER_ID_1;
}
else if(parsedQuery[0].equalsIgnoreCase("id") && parsedQuery[1].equalsIgnoreCase("2")){
responseString = TEACHER_ID_2;
}
else {
responseString = "";
}
response = new Response.Builder()
.code(200)
.message(responseString)
.request(chain.request())
.protocol(Protocol.HTTP_1_0)
.body(ResponseBody.create(MediaType.parse("application/json"), responseString.getBytes()))
.addHeader("content-type", "application/json")
.build();
}
else {
response = chain.proceed(chain.request());
}
return response;
}
}
GitHub のプロジェクトのソースコード
オブジェクトへのJSONデシリアライゼーションのテスト(おそらくTypeAdapters
?を使用)は、個別の単体テストを必要とする個別の問題のように思えます。
個人的にバージョン2を使用しています。タイプセーフで、リファクタリングしやすいコードを提供し、簡単にデバッグおよび変更できます。結局のところ、テスト用にAPIの代替バージョンを作成していない場合、APIをインターフェイスとして宣言するのは良いことです。勝利のための多態性。
別のオプションは、Java Proxy
を使用することです。これは実際に、Retrofitが(現在)基になるHTTP対話を実装する方法です。これは明らかにより多くの作業を必要としますが、はるかに動的なモックが可能になります。
SquareupのWebservermockなどを使用することもできます! -> https://github.com/square/okhttp/tree/master/mockwebserver
私は Apiary.io の大ファンです。実サーバーに移行する前にAPIをモックするためです。
フラット.jsonファイルを使用して、ファイルシステムから読み取ることもできます。
また、Twitter、Flickrなどの公にアクセス可能なAPIを使用することもできます。
以下に、Retrofitに関するその他の優れたリソースを示します。
スライド: https://docs.google.com/presentation/d/12Eb8OPI0PDisCjWne9-0qlXvp_-R4HmqVCjigOIgwfY/edit#slide=id.p
ビデオ: http://www.youtube.com/watch?v=UtM06W51pPw&feature=g-user-
サンプルプロジェクト: https://github.com/dustin-graham/ucad_Twitter_retrofit_sample
最初に、レトロフィットインターフェイスを作成します。
public interface LifeKitServerService {
/**
* query event list from server,convert Retrofit's Call to RxJava's Observerable
*
* @return Observable<HttpResult<List<Event>>> event list from server,and it has been convert to Obseverable
*/
@GET("api/event")
Observable<HttpResult<List<Event>>> getEventList();
}
次のリクエスタ:
public final class HomeDataRequester {
public static final String TAG = HomeDataRequester.class.getSimpleName();
public static final String SERVER_ADDRESS = BuildConfig.DATA_SERVER_ADDR + "/";
private LifeKitServerService mServerService;
private HomeDataRequester() {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
//using okhttp3 interceptor fake response.
.addInterceptor(new MockHomeDataInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(SERVER_ADDRESS)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.build();
//using okhttp3 inteception to fake response.
mServerService = retrofit.create(LifeKitServerService.class);
//Second choice,use MockRetrofit to fake data.
//NetworkBehavior behavior = NetworkBehavior.create();
//MockRetrofit mockRetrofit = new MockRetrofit.Builder(retrofit)
// .networkBehavior(behavior)
// .build();
//mServerService = new MockLifeKitServerService(
// mockRetrofit.create(LifeKitServerService.class));
}
public static HomeDataRequester getInstance() {
return InstanceHolder.sInstance;
}
public void getEventList(Subscriber<HttpResult<List<Event>>> subscriber) {
mServerService.getEventList()
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
}
2番目の選択肢を使用する場合(Mockサーバーデータへのレトロフィットインターフェイスを使用)、MockRetrofitを使用する必要があります。次のコードを使用します。
public final class MockLifeKitServerService implements LifeKitServerService {
public static final String TAG = MockLifeKitServerService.class.getSimpleName();
private BehaviorDelegate<LifeKitServerService> mDelegate;
private Gson mGson = new Gson();
public MockLifeKitServerService(BehaviorDelegate<LifeKitServerService> delegate) {
mDelegate = delegate;
}
@Override
public Observable<HttpResult<List<Event>>> getEventList() {
List<Event> eventList = MockDataGenerator.generateEventList();
HttpResult<List<Event>> httpResult = new HttpResult<>();
httpResult.setCode(200);
httpResult.setData(eventList);
LogUtil.json(TAG, mGson.toJson(httpResult));
String text = MockDataGenerator.getMockDataFromJsonFile("server/EventList.json");
if (TextUtils.isEmpty(text)) {
text = mGson.toJson(httpResult);
}
LogUtil.d(TAG, "Text:\n" + text);
text = mGson.toJson(httpResult);
return mDelegate.returningResponse(text).getEventList();
}
4.私のデータは資産ファイル(Asset/server/EventList.json)からのもので、このファイルの内容は次のとおりです。
{
"code": 200,
"data": [
{
"uuid": "e4beb3c8-3468-11e6-a07d-005056a05722",
"title": "title",
"image": "http://image.jpg",
"goal": 1500000,
"current": 51233,
"hot": true,
"completed": false,
"createdAt": "2016-06-15T04:00:00.000Z"
}
]
}
5. okhttp3インターセプターを使用している場合、次のようにインターセプターを自己定義する必要があります:
public final class MockHomeDataInterceptor implements Interceptor {
public static final String TAG = MockHomeDataInterceptor.class.getSimpleName();
@Override
public Response intercept(Chain chain) throws IOException {
Response response = null;
String path = chain.request().url().uri().getPath();
LogUtil.d(TAG, "intercept: path=" + path);
response = interceptRequestWhenDebug(chain, path);
if (null == response) {
LogUtil.i(TAG, "intercept: null == response");
response = chain.proceed(chain.request());
}
return response;
}
private Response interceptRequestWhenDebug(Chain chain, String path) {
Response response = null;
if (BuildConfig.DEBUG) {
Request request = chain.request();
if (path.equalsIgnoreCase("/api/event")) {
//get event list
response = getMockEventListResponse(request);
}
}
private Response getMockEventListResponse(Request request) {
Response response;
String data = MockDataGenerator.getMockDataFromJsonFile("server/EventList.json");
response = getHttpSuccessResponse(request, data);
return response;
}
private Response getHttpSuccessResponse(Request request, String dataJson) {
Response response;
if (TextUtils.isEmpty(dataJson)) {
LogUtil.w(TAG, "getHttpSuccessResponse: dataJson is empty!");
response = new Response.Builder()
.code(500)
.protocol(Protocol.HTTP_1_0)
.request(request)
//protocol&request be set,otherwise will be exception.
.build();
} else {
response = new Response.Builder()
.code(200)
.message(dataJson)
.request(request)
.protocol(Protocol.HTTP_1_0)
.addHeader("Content-Type", "application/json")
.body(ResponseBody.create(MediaType.parse("application/json"), dataJson))
.build();
}
return response;
}
}
6.最後に、コードでサーバーをリクエストできます:
mHomeDataRequester.getEventList(new Subscriber<HttpResult<List<Event>>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
LogUtil.e(TAG, "onError: ", e);
if (mView != null) {
mView.onEventListLoadFailed();
}
}
@Override
public void onNext(HttpResult<List<Event>> httpResult) {
//Your json result will be convert by Gson and return in here!!!
});
}
読んでくれてありがとう。
Mockery (免責事項:私は著者です)は、まさにこのタスクのために設計されました。
Mockeryは、Retrofitのサポートが組み込まれたネットワークレイヤーの検証に焦点を当てたモック/テストライブラリです。指定されたApiの仕様に基づいてJUnitテストを自動生成します。アイデアは、テストを手動で記述する必要がないことです。また、サーバー応答をモックするためのインターフェースも実装していません。
@Alecによる回答に加えて、リクエストURLに応じてアセットフォルダー内のテキストファイルから直接応答を取得するように模擬クライアントを拡張しました。
Ex
@POST("/activate")
public void activate(@Body Request reqdata, Callback callback);
ここで、模擬クライアントは、起動されるURLがアクティブであることを理解し、assetsフォルダーでactivate.txtという名前のファイルを探します。 asset/activate.txtファイルからコンテンツを読み取り、APIの応答として送信します。
拡張MockClient
は次のとおりです
public class MockClient implements Client {
Context context;
MockClient(Context context) {
this.context = context;
}
@Override
public Response execute(Request request) throws IOException {
Uri uri = Uri.parse(request.getUrl());
Log.d("MOCK SERVER", "fetching uri: " + uri.toString());
String filename = uri.getPath();
filename = filename.substring(filename.lastIndexOf('/') + 1).split("?")[0];
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
e.printStackTrace();
}
InputStream is = context.getAssets().open(filename.toLowerCase() + ".txt");
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
String responseString = new String(buffer);
return new Response(request.getUrl(), 200, "nothing", Collections.EMPTY_LIST, new TypedByteArray("application/json", responseString.getBytes()));
}
}
詳細な説明については、私のブログをご覧ください
http://www.cumulations.com/blogs/13/Mock-API-response-in-Retrofit-using-custom-clients
https://jsonplaceholder.typicode.com/
カスタマイズされた応答ペイロードをテストする場合、上記の2つは要件に合わない可能性があるため、ポストマンモックサーバーを試すことができます。独自の要求および応答ペイロードを定義するのは非常に簡単で、柔軟に設定できます。
https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/https://youtu.be/shYn3Ys3ygE
Retrofitでのapi呼び出しのモックはMockinizerでさらに簡単になりました。これにより、MockWebServerを簡単に操作できます。
import com.appham.mockinizer.RequestFilter
import okhttp3.mockwebserver.MockResponse
val mocks: Map<RequestFilter, MockResponse> = mapOf(
RequestFilter("/mocked") to MockResponse().apply {
setResponseCode(200)
setBody("""{"title": "Banana Mock"}""")
},
RequestFilter("/mockedError") to MockResponse().apply {
setResponseCode(400)
}
)
RequestFilterとMockResponsesのマップを作成し、OkHttpClientビルダーチェーンにプラグインするだけです。
OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.mockinize(mocks) // <-- just plug in your custom mocks here
.build()
MockWebServerの設定などについて心配する必要はありません。残りのすべてがMockinizerによって行われるようにモックを追加するだけです。
(免責事項:私はMockinizerの著者です)
私にとって、カスタムRetrofit Clientは柔軟性のため素晴らしいです。特に、DIフレームワークを使用する場合、モックをすばやく簡単にオン/オフできます。ユニットテストと統合テストでも、Daggerが提供するカスタムクライアントを使用しています。
編集:ここでは、モック改造の例を見つける https://github.com/pawelByszewski/retrofitmock