Server di retrofit quadrato finto per test


97

Qual è il modo migliore per simulare un server per i test quando si utilizza il framework di retrofit quadrato .

Possibili modi:

  1. Crea un nuovo client di retrofit e impostalo in RestAdapter.Builder (). SetClient (). Ciò comporta l'analisi dell'oggetto Request e la restituzione del json come oggetto Response.

  2. Implementa questa interfaccia annotata come una classe fittizia e usala al posto della versione fornita da RestAdapter.create () (non testare la serializzazione gson)

  3. ?

Idealmente, voglio che il server deriso fornisca risposte json in modo da poter testare la serializzazione gson allo stesso tempo.

Qualsiasi esempio sarebbe molto apprezzato.


@ JakeWharton, qual è lo scopo di square-oss? Sembra ridondante dato retrofit.
Charles

@ Alec Holmes: hai risolto il tuo problema?
AndiGeeky

Risposte:


104

Richieste di Mock Retrofit 2.0 per il test

Poiché i vecchi meccanismi come la creazione di MockClientclassi e l'implementazione da cui Clientnon funzionano più con Retrofit 2.0, qui descrivo un nuovo modo di farlo. Tutto quello che devi fare ora è aggiungere i tuoi intercettori personalizzati per OkHttpClient come mostrato di seguito . FakeInterceptorclass sovrascrive solo il interceptmetodo e nel caso in cui l'applicazione sia in DEBUGmodalità restituisce 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;
    }
}

Codice sorgente del progetto su GitHub


9
Per evitare UnsupportedOperationException utilizzare OkHttpClient.Builder. finale OkHttpClient okHttpClient = new OkHttpClient.Builder () .addInterceptor (new FakeInterceptor ()) .build ();
John

4
Due problemi che ho: 1- Non c'è nessun uri()sotto chain.request().uri()(ho risolto questo problema in String url = chain.request().url().toString();quanto il mio caso è diverso). 2- Sto ottenendo java.lang.IllegalStateException: network interceptor my.package.name.FakeInterceptor must call proceed() exactly once. L'ho aggiunto a addNetworkInterceptor()piuttosto che addInterceptor().
Hesam

2
usa chain.request (). url (). uri ();
Amol Gupta

Come posso simulare l'errore 401 per testare il metodo httpClient.authenticator? semplicemente inserendo il codice "401" il metodo di autenticazione non chiama. come posso gestirlo?
Mahdi

Ho portato il falso approccio dell'intercettore per deridere le API web al livello successivo e ho pubblicato una piccola libreria per renderlo ancora più semplice e conveniente. Vedi github.com/donfuxx/Mockinizer
donfuxx il

85

Ho deciso di provare il metodo 1 come segue

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

E usandolo da:

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

Funziona bene e ti permette di testare le tue stringhe json senza dover contattare il server reale!


Ho aggiornato il costruttore di risposta utilizzato poiché quello vecchio era deprecato, che stava lanciando un IllegalArgumentException url == nullcon Retrofit 1.4.1.
Dan J

1
Inoltre è necessario aggiungere un endpoint al builder:builder.setEndpoint("http://mockserver.com").setClient(new MockClient());
codeprogression

Ho esteso il client fittizio sopra per recuperare la risposta da un file nella cartella delle risorse a seconda della richiesta dell'URL.
praveena_kd

21
Retrofit 2 ora utilizza OkHttpClient per il livello client e questo codice non funziona. ¿Qualche idea su come realizzare una simulazione di OkHttpClient? Probabilmente si tratta di estenderlo e sovrascriverlo, ma non sono sicuro di come.
GuillermoMP

1
Puoi aggiornare la tua risposta anche in base a Retrofit2? grazie
Hesam

20

Testare la deserializzazione JSON sui tuoi oggetti (presumibilmente con TypeAdapters?) Sembra un problema separato che richiede unit test separati.

Uso personalmente la versione 2. Fornisce codice indipendente dai tipi e compatibile con il refactor che può essere facilmente sottoposto a debug e modificato. Dopo tutto, a che serve dichiarare la tua API come interfacce se non stai creando versioni alternative di esse per il test! Polimorfismo per la vittoria.

Un'altra opzione è usare Java Proxy. Questo è in realtà il modo in cui Retrofit (attualmente) implementa la sua interazione HTTP sottostante. Ciò richiederà certamente più lavoro, ma consentirebbe simulazioni molto più dinamiche.


Questo è anche il mio modo preferito. È molto più semplice eseguire il debug come indicato sopra, quindi dover trattare direttamente con il corpo della risposta. @alec Se si desidera testare la serializzazione GSON, generare / leggere una stringa json e utilizzare un oggetto gson per deserializzare. Sotto la testa credo che sia quello che fa comunque Retrofit.
loeschg

@ JakeWharton Potresti fornire un breve esempio di ciò che vorrebbe? Ho problemi a visualizzare questo ... Grazie!
uncle_tex



8

Sono un grande fan di Apiary.io per aver deriso un'API prima di passare a un server reale.

È inoltre possibile utilizzare file .json flat e leggerli dal file system.

Puoi anche utilizzare API pubblicamente accessibili come Twitter, Flickr, ecc.

Ecco alcune altre fantastiche risorse su Retrofit.

Diapositive: 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

Progetto di esempio: https://github.com/dustin-graham/ucad_twitter_retrofit_sample


7

Mockery (disclaimer: I'm the author) è stato progettato proprio per questo compito.

Mockery è una libreria di simulazione / test incentrata sulla convalida dei livelli di rete con supporto integrato per Retrofit. Genera automaticamente i test JUnit in base alle specifiche di una determinata API. L'idea è di non dover scrivere manualmente alcun test; né implementare interfacce per deridere le risposte del server.


7
  1. Per prima cosa, crea la tua interfaccia Retrofit.

    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. Il tuo richiedente a seguire:

    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. Se si utilizza la seconda scelta (utilizzare l'interfaccia Retrofit per i dati del server Mock), è necessario utilizzare MockRetrofit, utilizzare il codice seguente:

    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.I miei dati provengono dal file di risorse (Asset / server / EventList.json), il contenuto di questo file è:

    {
      "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.Se stai usando okhttp3 interceptor, devi auto-definire l'interceptor, in questo modo:

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.Infine, puoi richiedere il tuo server con il codice:

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

Grazie per aver letto.


5

Aggiungendo alla risposta di @Alec, ho esteso il client fittizio per ottenere la risposta direttamente da un file di testo nella cartella delle risorse a seconda dell'URL della richiesta.

Ex

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

Qui il finto client, capisce che l'URL che viene sparato è attivato e cerca un file chiamato Activate.txt nella cartella degli asset. Legge il contenuto dal file assets / activation.txt e lo invia come risposta per l'API.

Ecco l'estensione 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()));
    }
}

Per una spiegazione dettagliata puoi controllare il mio blog
http://www.cumulations.com/blogs/13/Mock-API-response-in-Retrofit-using-custom-clients


ciao, Quando scrivo una classe di test usando robolectric e usando un client fittizio per deridere l'API retrofit, non mi dà alcuna risposta. Potresti guidarmi come fare questo.
Dory

Ciao @Dory assicurati di avere la parte finale dell'URL e il nome del file all'interno della cartella delle risorse. Ad esempio, supponiamo che il tuo URL sia il seguente (usando Reftrofit qui) @POST ("/ redeemGyft") public void redeemGyft (@Body MposRequest reqdata, Callback <RedeemGyftResponse> callback); quindi il nome del file corrispondente nella cartella delle risorse è redeemgyft.txt
praveena_kd

Ho dato un nome al file statico, nel mio MockClientfile, scritto una classe di test usando robolectric. Ma non sono in grado di ottenere alcuna risposta dal file json.
Dory

se hai tenuto il file all'interno della cartella delle risorse, dovrebbe raccoglierlo.
praveena_kd

1

JSONPlaceholder: API REST online falsa per test e prototipi

https://jsonplaceholder.typicode.com/

ReqresIn: un'altra API REST online

https://reqres.in/

Server finto postino

Se desideri testare il payload di risposta personalizzato, i due precedenti potrebbero non soddisfare le tue esigenze, puoi provare il server fittizio di postino. È abbastanza facile da configurare e flessibile per definire il proprio payload di richiesta e risposta.

inserisci qui la descrizione dell'immagine https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/ https://youtu.be/shYn3Ys3ygE


1

Deridere le chiamate API con Retrofit è ora ancora più semplice con Mockinizer che rende il lavoro con MockWebServer davvero semplice:

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

)

Basta creare una mappa di RequestFilter e MockResponses e quindi collegarla alla catena di builder OkHttpClient:

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

Non devi preoccuparti di configurare MockWebServer ecc. Aggiungi semplicemente i tuoi mock, il resto è fatto da Mockinizer per te.

(Dichiarazione di non responsabilità: sono l'autore di Mockinizer)


0

Per me il client di retrofit personalizzato è ottimo grazie alla flessibilità. Soprattutto quando si utilizza qualsiasi framework DI, è possibile attivare / disattivare in modo rapido e semplice il mock. Sto utilizzando un client personalizzato fornito da Dagger anche nei test di unità e di integrazione.

Modifica: qui trovi un esempio di derisione del retrofit https://github.com/pawelByszewski/retrofitmock

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.