wake-up-neo.net

Quadratischer Retrofit-Server-Mock zum Testen

Was ist der beste Weg, um einen Server für das Testen zu simulieren, wenn Sie das square-Retrofit-Framework verwenden.

Mögliche Wege:

  1. Erstellen Sie ein neues Retrofit client und setzen Sie es in RestAdapter.Builder (). SetClient (). Dazu wird das Request-Objekt analysiert und der Json als Response-Objekt zurückgegeben.

  2. Implementieren Sie diese annotierte Schnittstelle als Mock-Klasse und verwenden Sie diese anstelle der Version, die von RestAdapter.create () bereitgestellt wird.

  3. ?

Idealerweise möchte ich, dass der verspottete Server Json-Antworten bereitstellt, damit ich die Gson-Serialisierung gleichzeitig testen kann.

Alle Beispiele wären sehr dankbar.

78
Alec Holmes

Ich entschied mich für Methode 1 wie folgt

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

Und es benutzen von:

RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setClient(new MockClient());

Es funktioniert gut und ermöglicht es Ihnen, Ihre Json-Strings zu testen, ohne den echten Server kontaktieren zu müssen!

78
Alec Holmes

Mock Retrofit 2.0-Anforderungen zum Testen

Da die alten Mechanismen, wie das Erstellen einer MockClient-Klasse und deren Implementierung aus Client, nicht mehr mit Retrofit 2.0 funktionieren, beschreibe ich hier einen neuen Weg. Alles, was Sie jetzt tun müssen, ist fügen Sie Ihre benutzerdefinierten Interceptors für OkHttpClient hinzu, wie unten gezeigt}. Die FakeInterceptor-Klasse überschreibt lediglich die intercept-Methode. Falls sich die Anwendung im DEBUG-Modus befindet, geben Sie den angegebenen JSON-Wert zurück. 

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

Quellcode des Projekts auf GitHub

87
Victor Apoyan

Das Testen der JSON-Deserialisierung für Ihre Objekte (vermutlich mit TypeAdapters?) Scheint ein separates Problem zu sein, das separate Komponententests erfordert.

Ich benutze Version 2 persönlich. Es bietet typsicheren, Refactor-freundlichen Code, der leicht debuggt und geändert werden kann. Was nützt es schließlich, Ihre API als Schnittstellen zu deklarieren, wenn Sie keine alternativen Versionen davon für Tests erstellen! Polymorphismus für den Sieg.

Eine andere Option ist die Verwendung von Java Proxy. Auf diese Weise implementiert Retrofit (derzeit) die zugrunde liegende HTTP-Interaktion. Dies erfordert zwar mehr Arbeit, würde aber viel dynamischere Vorstöße zulassen.

19
Jake Wharton

Ich bin ein großer Fan von Apiary.io für ein API-Spott, bevor er zu einem echten Server wechselt.

Sie können auch flache .json-Dateien verwenden und aus dem Dateisystem lesen. 

Sie können auch öffentlich zugängliche APIs wie Twitter, Flickr usw. verwenden. 

Hier finden Sie weitere großartige Ressourcen zu Retrofit. 

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

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

Beispielprojekt: https://github.com/dustin-graham/ucad_Twitter_retrofit_sample

7
jpotts18

Sie können auch etwas wie Webservermock von Squareup verwenden! -> https://github.com/square/okhttp/tree/master/mockwebserver

7
mato
  1. Erstellen Sie zunächst Ihre Retrofit-Schnittstelle.

    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. Ihr Anforderer folgt:

    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. Wenn Sie die zweite Wahl verwenden (Retrofit-Schnittstelle für Mock-Serverdaten verwenden), müssen Sie MockRetrofit verwenden. Verwenden Sie folgenden Code:

    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.Meine Daten stammen aus einer Asset-Datei (Asset/Server/EventList.json). Der Inhalt dieser Datei lautet:

    {
      "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.Wenn Sie den Interceptor okhttp3 verwenden, müssen Sie den Interceptor selbst definieren, wie folgt:

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.Schließlich können Sie Ihren Server mit Code anfordern:

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

Danke fürs Lesen.

7
Lai ZuLing

Mockery (Disclaimer: Ich bin der Autor) wurde genau für diese Aufgabe entwickelt. 

Mockery ist eine Verspottungs-/Testbibliothek, die sich auf die Überprüfung von Netzwerkebenen mit integrierter Unterstützung für Retrofit konzentriert. Es generiert automatisch JUnit-Tests basierend auf den Spezifikationen einer bestimmten API. Die Idee ist, keinen Test manuell zu schreiben; auch keine Implementierung von Schnittstellen zur Verspottung von Serverantworten. 

6

Zusätzlich zur Antwort von @Alec habe ich den Scheinclient erweitert, um die Antwort direkt aus einer Textdatei im Asset-Ordner abhängig von der Anforderungs-URL zu erhalten.

Ex

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

Hier erkennt der Mock-Client, dass die URL, die ausgelöst wird, aktiviert ist, und sucht im Assets-Ordner nach einer Datei mit dem Namen enable.txt . Er liest den Inhalt aus der Datei assets/disable.txt und sendet sie als Antwort für die API.

Hier ist die erweiterte 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()));
    }
}

Für eine ausführliche Erklärung können Sie meinen Blog besuchen 
http://www.cumulations.com/blogs/13/Mock-API-response-in-Retrofit- using-custom-clients

5
praveena_kd

JSONPlaceholder: Fake Online REST API zum Testen und Prototyping

https://jsonplaceholder.typicode.com/

ReqresIn: Eine weitere Online REST-API

https://reqres.in/

Postman Mock Server

Wenn Sie eine angepasste Antwortnutzlast testen möchten, die oben genannten beiden möglicherweise nicht Ihren Anforderungen entsprechen, können Sie den Postman-Mock-Server ausprobieren. Es ist recht einfach einzurichten und flexibel, Ihre eigenen Nutzdaten für Anforderungen und Antworten zu definieren.

enter image description herehttps://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/https://youtu.be/shYn3Ys3ygE

1
li2

Das Verspotten von API-Aufrufen mit Retrofit ist jetzt noch einfacher mit Mockinizer, was die Arbeit mit MockWebServer wirklich unkompliziert macht:

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

)

Erstellen Sie einfach eine Zuordnung von RequestFilter und MockResponses und fügen Sie sie in Ihre OkHttpClient-Builderkette ein:

OkHttpClient.Builder()
            .addInterceptor(loggingInterceptor)
            .mockinize(mocks) // <-- just plug in your custom mocks here
            .build()

Sie müssen sich nicht um die Konfiguration von MockWebServer usw. kümmern. Fügen Sie einfach Ihre Mocks hinzu, der Rest wird von Mockinizer für Sie erledigt.

(Haftungsausschluss: Ich bin der Autor von Mockinizer)

0
donfuxx

Für mich ist der benutzerdefinierte Retrofit-Client aufgrund seiner Flexibilität großartig. Insbesondere wenn Sie ein DI-Framework verwenden, können Sie schnell und einfach Mock ein- und ausschalten. Ich verwende auch den von Dagger zur Verfügung gestellten benutzerdefinierten Client für Unit- und Integrationstests. 

Edit: Hier finden Sie ein Beispiel für einen verspäteten Retrofit https://github.com/pawelByszewski/retrofitmock

0