web-dev-qa-db-fra.com

Carré de modernisation de serveur simulé pour les tests

Quelle est la meilleure façon de simuler un serveur pour les tests lorsque vous utilisez le framework square retrofit .

Manières potentielles:

  1. Créez un nouveau fichier client et définissez-le dans RestAdapter.Builder (). SetClient (). Cela implique d'analyser l'objet Request et de renvoyer le JSON en tant qu'objet Response.

  2. Implémentez cette interface annotée en tant que classe fictive et utilisez-la à la place de la version fournie par RestAdapter.create () (sans test de la sérialisation gson)

  3. ?

Idéalement, je souhaite que le serveur simulé fournisse des réponses json afin que je puisse tester la sérialisation gson en même temps.

Tous les exemples seraient grandement appréciés.

78
Alec Holmes

J'ai décidé d'essayer la méthode 1 comme suit

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

Et l'utiliser en:

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

Cela fonctionne bien et vous permet de tester vos chaînes json sans avoir à contacter le vrai serveur!

78
Alec Holmes

Demandes de test de modification simulée 2.0

Comme les anciens mécanismes tels que la création de la classe MockClient et son implémentation à partir de Client ne fonctionnent plus avec Retrofit 2.0, je décris ici une nouvelle façon de procéder. Tout ce que vous devez faire maintenant est de ajouter vos intercepteurs personnalisés pour OkHttpClient, comme indiqué ci-dessous. La classe FakeInterceptor remplace simplement la méthode intercept. Dans le cas où l'application est en mode DEBUG, le code JSON est renvoyé. 

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

Code source du projet sur GitHub

87
Victor Apoyan

Tester la désérialisation JSON sur vos objets (probablement avec TypeAdapters?) Semble être un problème distinct qui nécessite des tests unitaires distincts.

J'utilise la version 2 personnellement. Il offre un code adapté aux types et aux refactors, qui peut être facilement mis au point et modifié. Après tout, à quoi sert-il de déclarer votre API comme interfaces si vous n'en créez pas d'autres versions à des fins de test! Polymorphisme pour la victoire.

Une autre option utilise une variable Java Proxy. C’est en fait la façon dont Retrofit (actuellement) implémente son interaction HTTP sous-jacente. Cela nécessitera certes plus de travail, mais permettrait des simulacres beaucoup plus dynamiques.

19
Jake Wharton

Je suis un grand fan de Apiary.io pour avoir moqué une API avant de passer à un vrai serveur.

Vous pouvez également utiliser des fichiers .json plats et les lire à partir du système de fichiers. 

Vous pouvez également utiliser des API accessibles au public, telles que Twitter, Flickr, etc. 

Voici d’autres ressources intéressantes sur la modernisation. 

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

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

Exemple de projet: https://github.com/dustin-graham/ucad_Twitter_retrofit_sample

7
jpotts18

Vous pouvez également utiliser quelque chose comme Webservermock de Squareup! -> https://github.com/square/okhttp/tree/master/mockwebserver

7
mato
  1. Tout d’abord, créez votre interface 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. Votre demandeur à suivre:

    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. Si vous utilisez le deuxième choix (utilisez l'interface de modification pour simuler les données du serveur), vous devez effectuer une rétrocession, le code suivant

    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. Mes données proviennent du fichier d'actif (Asset/server/EventList.json), le contenu de ce fichier est:

    {
      "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.Si vous utilisez l'intercepteur okhttp3, vous devez définir lui-même un intercepteur, comme ceci:

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.Enfin, vous pouvez demander à votre serveur avec le code:

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

Merci d'avoir lu.

7
Lai ZuLing

Mockery (disclaimer: je suis l'auteur) a été conçu uniquement pour cette tâche. 

Mockery est une bibliothèque de moquages ​​/ tests axée sur la validation des couches réseau avec prise en charge intégrée de Retrofit. Il génère automatiquement des tests JUnit en fonction des spécifications d'une API donnée. L'idée est de ne pas avoir à écrire manuellement un test; ni implémenter des interfaces pour mocking réponses du serveur. 

6
Víctor Albertos

En ajoutant à la réponse de @Alec, j'ai étendu le client fictif pour obtenir la réponse directement à partir d'un fichier texte dans le dossier des ressources en fonction de l'URL de la demande.

Ex

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

Ici, le client fictif comprend que l'URL déclenchée est Activé et recherche un fichier nommé activate.txt dans le dossier assets . Il lit le contenu du fichier assets/activate.txt et l'envoie en réponse à l'API.

Voici la MockClient étendue

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

Pour une explication détaillée, vous pouvez consulter mon blog 
http://www.cumulations.com/blogs/13/Mock-API-response-in-Retrofit-using-custom-clients

5
praveena_kd

JSONPlaceholder: Fake Online REST API de test et de prototypage

https://jsonplaceholder.typicode.com/

ReqresIn: une autre API en ligne REST

https://reqres.in/

Serveur factice Postman

Si vous souhaitez tester la charge de réponse personnalisée, il est possible que les deux réponses ci-dessus ne répondent pas à vos besoins. Vous pouvez ensuite essayer postman mock server. Il est assez facile à configurer et flexible de définir votre propre charge utile de demande et de réponse.

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

1
li2

Mocker des appels d'api avec Retrofit est maintenant encore plus facile avec Mockinizer, ce qui rend le travail avec MockWebServer vraiment simple:

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

)

Créez simplement une carte de RequestFilter et MockResponses , puis branchez-la dans votre chaîne de générateur OkHttpClient:

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

Vous n'avez pas à vous soucier de la configuration de MockWebServer, etc. Ajoutez simplement vos simulacres, tout le reste est fait par Mockinizer pour vous.

(Avertissement: je suis l'auteur de Mockinizer)

0
donfuxx

Pour moi, le client de modernisation personnalisé est excellent en raison de sa flexibilité. Surtout lorsque vous utilisez un cadre DI, vous pouvez rapidement et simplement activer/désactiver la simulation. J'utilise un client personnalisé fourni par Dagger également dans des tests unitaires et d'intégration. 

Edit: Vous trouverez ici un exemple de rénovation moqueuse https://github.com/pawelByszewski/retrofitmock

0
Paweł Byszewski