テスト用の正方形レトロフィットサーバーモック


97

正方形のレトロフィットフレームワークを使用する場合、サーバーを模擬的にテストするための最良の方法は何ですか。

可能な方法:

  1. 新しいレトロフィットクライアントを作成し、RestAdapter.Builder()。setClient()に設定します。これには、Requestオブジェクトを解析し、jsonをResponseオブジェクトとして返すことが含まれます。

  2. この注釈付きインターフェースをモッククラスとして実装し、RestAdapter.create()によって提供されるバージョンの代わりにそれを使用します(gsonのシリアル化はテストしません)

理想的には、モックされたサーバーにjson応答を提供して、同時にgsonシリアル化をテストできるようにしたいです。

どんな例でも大歓迎です。


@JakeWharton、何の目的square-ossですか?与えられretrofitた冗長なようです。
Charles

@アレック・ホームズ:あなたは問題を解決しましたか?
AndiGeeky 16

回答:


104

テストのための模擬レトロフィット2.0リクエスト

MockClientクラスの作成やクラスからの実装などの古いメカニズムClientはRetrofit 2.0では機能しなくなったため、ここで新しい方法を説明します。次に必要なのは、以下に示すように、OkHttpClientのカスタムインターセプター追加することだけですFakeInterceptorクラスはinterceptメソッドをオーバーライドするだけで、アプリケーションがDEBUGモードの場合は与えられたJSONを返します。

RestClient.java

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;
    }
}

IRestService.java

public interface IRestService {

    String ENDPOINT = "http://www.vavian.com/";

    @GET("/")
    Call<Teacher> getTeacherById(@Query("id") final String id);
}

FakeInterceptor.java

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上のプロジェクトのソースコード


9
UnsupportedOperationExceptionを回避するには、OkHttpClient.Builderを使用します。最終的なOkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new FakeInterceptor()).build();
ジョン

4
私が持っている2つの問題:1- uri()アンダーはありませんchain.request().uri()(これString url = chain.request().url().toString();は私の場合とは異なるので修正しました)。2-取得していjava.lang.IllegalStateException: network interceptor my.package.name.FakeInterceptor must call proceed() exactly onceます。addNetworkInterceptor()ではなくに追加しましたaddInterceptor()
ヘサム

2
chain.request()。url()。uri();を使用してください
Amol Gupta 2017

401エラーをモックしてhttpClient.authenticatorメソッドをテストするにはどうすればよいですか?コード「401」を置くだけで、認証メソッドは呼び出されません。どうすればこれを処理できますか?
Mahdi、

私は偽のインターセプター手法を使用してWeb APIをモックアップし、さらに簡単で便利にするための小さなライブラリーを公開しました。github.com/donfuxx/Mockinizer
donfuxxを

85

次の方法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文字列をテストできます。


IllegalArgumentException url == nullRetrofit 1.4.1 でをスローしていた古いコンストラクターが非推奨になったため、Responseコンストラクターを更新しました。
Dan J

1
また、ビルダーにエンドポイントを追加する必要がありますbuilder.setEndpoint("http://mockserver.com").setClient(new MockClient());
codeprogression

上記のモッククライアントを拡張して、URLリクエストに応じてアセットフォルダ内のファイルから応答をフェッチしました。
praveena_kd

21
Retrofit 2はクライアントレイヤーにOkHttpClientを使用するようになり、このコードは機能しません。anOkHttpClientをモックにする方法はありますか?多分それはそれを拡張し、オーバーライドすることに関するすべてですが、私はどのようにしたらいいのかわかりません。
GuillermoMP

1
Retrofit2に基づく回答も更新できますか?ありがとう
Hesam

20

オブジェクトへのJSON逆シリアル化のテスト(おそらくTypeAdapters?を使用)は、個別の単体テストを必要とする個別の問題のようです。

私は個人的にバージョン2を使用しています。簡単にデバッグおよび変更できる、タイプセーフでリファクタリングに適したコードを提供します。結局のところ、テスト用にAPIの代替バージョンを作成しないのであれば、APIをインターフェースとして宣言するのは良いことです。勝利のための多態性。

別のオプションは、Javaを使用することですProxy。これは実際、Retrofitが(現在)その基礎となるHTTP相互作用を実装する方法です。これは明らかにより多くの作業を必要としますが、はるかに動的なモックを可能にします。


これも私の好みの方法です。上記のようにデバッグする方がはるかに簡単で、応答本体を直接処理する必要があります。@alec GSONのシリアル化をテストする場合は、json文字列を生成/読み込み、gsonオブジェクトを使用して逆シリアル化します。頭の中で私はそれがレトロフィットがとにかくやっていることだと信じています。
loeschg 14年

@JakeWhartonこれが欲しいものの短い例を提供してくれませんか?これの視覚化に問題があります...ありがとう!
uncle_tex

1
@uncle_texgithub.com/ JakeWharton
u2020/


8

私は、実際のサーバーに移動する前にAPIをモックするApiary.ioの大ファンです。

フラット.jsonファイルを使用して、ファイルシステムから読み取ることもできます。

Twitter、Flickrなどの一般公開されているAPIを使用することもできます。

次に、レトロフィットに関するその他の優れたリソースを示します。

スライド:https : //docs.google.com/presentation/d/12Eb8OPI0PDisCjWne9-0qlXvp_-R4HmqVCjigOIgwfY/edit#slide=id.p

ビデオ:http : //www.youtube.com/watch?v=UtM06W51pPw&feature=g-user-u

サンプルプロジェクト:https : //github.com/dustin-graham/ucad_twitter_retrofit_sample


7

Mockery(免責事項:私は作成者です)は、まさにこのタスクのために設計されました。

Mockeryは、レトロフィットのサポートが組み込まれたネットワークレイヤーの検証に焦点を当てたモック/テストライブラリです。特定のAPIの仕様に基づいてJUnitテストを自動生成します。アイデアは手動でテストを書く必要がないということです。サーバー応答をモックするためのインターフェースも実装していません。


7
  1. まず、レトロフィットインターフェイスを作成します。

    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();
    }
  2. フォローしているリクエスタ:

    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);
        }
    }
  3. 2番目の選択肢(モックサーバーデータへのRetrofitインターフェイスを使用)を使用する場合は、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.Myデータはアセットファイル(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!!!
    });
}

読んでくれてありがとう。


5

@Alecによる回答に加えて、リクエストURLに応じてアセットフォルダ内のテキストファイルから直接応答を取得するようにモッククライアントを拡張しました。

@POST("/activate")
public void activate(@Body Request reqdata, Callback callback);

ここでモッククライアントは、起動されるURLがアクティブ化されていることを理解し、assetsフォルダーでactivate.txtという名前のファイルを探します。assets / 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


こんにちは、私がrobolectricを使用してモッククライアントを使用してレトロフィットAPIをモックするテストクラスを作成しているときに、応答がありません。これを行う方法を教えてください。
Dory

こんにちは@Doryアセットフォルダー内にURLの終了部分とファイル名があることを確認してください。たとえば、URLが次のようになっているとします(ここではReftrofitを使用)@POST( "/ redeemGyft")public void redeemGyft(@Body MposRequest reqdata、Callback <RedeemGyftResponse> callback); 次に、アセットフォルダ内の対応するファイル名はredeemgyft.txtです
praveena_kd

私のファイルでは、MockClientrobolectricを使用してテストクラスを記述した静的ファイル名を指定しています。しかし、私はjsonファイルから応答を得ることができません。
Dory

ファイルをアセットフォルダ内に保持している場合は、それを取得する必要があります。
praveena_kd

1

JSONPlaceholder:テストとプロトタイピングのための偽のオンラインREST API

https://jsonplaceholder.typicode.com/

ReqresIn:別のオンラインREST API

https://reqres.in/

Postmanモックサーバー

カスタマイズされた応答ペイロードをテストする場合は、上記の2つが要件に合わない可能性があるため、postmanモックサーバーを試すことができます。設定は非常に簡単で、独自のリクエストとレスポンスのペイロードを定義するのに柔軟性があります。

ここに画像の説明を入力してください https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/ https://youtu.be/shYn3Ys3ygE


1

Mockinizerを使用すると、Retrofitを使用したAPI呼び出しのモックがさらに簡単になり、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の作成者です)


0

私にとって、カスタムレトロフィットクライアントは柔軟性があるので素晴らしいです。特に、DIフレームワークを使用する場合は、高速かつ簡単にモックのオン/オフを切り替えることができます。単体テストと統合テストでも、Daggerが提供するカスタムクライアントを使用しています。

編集:ここでは、改造のモックの例を見つけ ますhttps://github.com/pawelByszewski/retrofitmock

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.