Quelle est la meilleure façon de simuler un serveur pour les tests lorsque vous utilisez le framework square retrofit .
Manières potentielles:
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.
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)
?
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.
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!
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é.
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;
}
}
Code source du projet sur GitHub
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.
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
Vous pouvez également utiliser quelque chose comme Webservermock de Squareup! -> https://github.com/square/okhttp/tree/master/mockwebserver
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();
}
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);
}
}
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.
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.
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
https://jsonplaceholder.typicode.com/
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.
https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/https://youtu.be/shYn3Ys3ygE
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)
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