Simulacro de servidor de actualización cuadrado para pruebas

97

¿Cuál es la mejor manera de simular un servidor para realizar pruebas cuando se usa el marco de actualización cuadrado ?

Formas potenciales:

  1. Cree un nuevo cliente de actualización y configúrelo en RestAdapter.Builder (). SetClient (). Esto implica analizar el objeto Request y devolver el json como un objeto Response.

  2. Implemente esta interfaz anotada como una clase simulada y úsela en lugar de la versión proporcionada por RestAdapter.create () (no probará la serialización gson)

  3. ?

Idealmente, quiero que el servidor simulado proporcione respuestas json para poder probar la serialización gson al mismo tiempo.

Cualquier ejemplo sería muy apreciado.

Alec Holmes
fuente
@JakeWharton, ¿cuál es el propósito square-oss? Parece redundante dado retrofit.
Charles
@Alec Holmes: ¿Resolvió su problema?
AndiGeeky

Respuestas:

104

Solicitudes de prueba de Mock Retrofit 2.0

Como los viejos mecanismos como crear una MockClientclase e implementarla desde Clientya no funcionan con Retrofit 2.0, aquí describo una nueva forma de hacerlo. Todo lo que necesita hacer ahora es agregar sus interceptores personalizados para OkHttpClient como se muestra a continuación . FakeInterceptorclass simplemente anula el interceptmétodo y, en el caso de que la aplicación esté en DEBUGmodo, devuelve JSON dado.

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

Código fuente del proyecto en GitHub

Víctor Apoyan
fuente
9
Para evitar UnsupportedOperationException, use OkHttpClient.Builder. OkHttpClient final okHttpClient = nuevo OkHttpClient.Builder () .addInterceptor (nuevo FakeInterceptor ()) .build ();
Juan
4
Tengo dos problemas: 1- No hay uri()debajo chain.request().uri()(lo arreglé String url = chain.request().url().toString();porque mi caso es diferente). 2- Estoy recibiendo java.lang.IllegalStateException: network interceptor my.package.name.FakeInterceptor must call proceed() exactly once. He añadido esto en addNetworkInterceptor()lugar de addInterceptor().
Hesam
2
use chain.request (). url (). uri ();
Amol Gupta
¿Cómo puedo simular el error 401 para probar el método httpClient.authenticator? con solo poner el código "401", el método de autenticación no realiza llamadas. ¿cómo puedo manejar esto?
Mahdi
He llevado el enfoque del interceptor falso para burlar las apis web al siguiente nivel y publiqué una pequeña biblioteca para que sea aún más fácil y conveniente. Ver github.com/donfuxx/Mockinizer
donfuxx
85

Decidí probar el método 1 de la siguiente manera

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

Y usándolo por:

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

¡Funciona bien y le permite probar sus cadenas json sin tener que contactar al servidor real!

Alec Holmes
fuente
Actualicé el constructor de Respuesta usado ya que el anterior estaba en desuso, que estaba lanzando un IllegalArgumentException url == nullcon Retrofit 1.4.1.
Dan J
1
También es necesario agregar un punto final al constructor:builder.setEndpoint("http://mockserver.com").setClient(new MockClient());
codeprogression
He extendido el cliente simulado anterior para obtener la respuesta de un archivo en la carpeta de activos según la solicitud de URL.
praveena_kd
21
Retrofit 2 ahora usa OkHttpClient para la capa de cliente y este código no funciona. ¿Alguna idea de cómo hacer un simulacro de OkHttpClient? Probablemente se trate de extenderlo y anularlo, pero no estoy seguro de cómo.
GuillermoMP
1
¿Puede actualizar su respuesta también en función de Retrofit2? gracias
Hesam
20

Probar la deserialización JSON en sus objetos (presumiblemente con TypeAdapters?) Parece un problema separado que requiere pruebas unitarias separadas.

Yo uso la versión 2 personalmente. Ofrece código de tipo seguro y fácil de refactorizar que puede depurarse y modificarse fácilmente. Después de todo, ¡de qué sirve declarar su API como interfaces si no está creando versiones alternativas de ellas para probarlas! Polimorfismo para ganar.

Otra opción es usar Java Proxy. Así es como Retrofit (actualmente) implementa su interacción HTTP subyacente. Es cierto que esto requerirá más trabajo, pero permitiría simulaciones mucho más dinámicas.

Jake Wharton
fuente
Esta es también mi forma preferida. Es mucho más sencillo depurar como se indicó anteriormente que tener que lidiar tan directamente con el cuerpo de respuesta. @alec Si desea probar la serialización GSON, genere / lea una cadena json y use un objeto gson para deserializar. Debajo de la cabeza, creo que eso es lo que Retrofit hace de todos modos.
Loeschg
@JakeWharton ¿Podría dar un breve ejemplo de lo que le gustaría a esto? Tengo problemas para visualizar esto ... ¡Gracias!
uncle_tex
1
@uncle_tex Echa un vistazo a github.com/JakeWharton/u2020/blob/master/src/internalDebug/java/…
riwnodennyk
8

Soy un gran admirador de Apiary.io para burlarse de una API antes de pasar a un servidor real.

También puede usar archivos .json planos y leerlos desde el sistema de archivos.

También puede utilizar API de acceso público como Twitter, Flickr, etc.

A continuación, se incluyen otros recursos excelentes sobre Retrofit.

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

Proyecto de ejemplo: https://github.com/dustin-graham/ucad_twitter_retrofit_sample

jpotts18
fuente
7

Mockery (descargo de responsabilidad: soy el autor) fue diseñado exactamente para esta tarea.

Mockery es una biblioteca de prueba / simulacro centrada en validar capas de red con soporte incorporado para Retrofit. Autogenera pruebas JUnit basadas en las especificaciones de una API determinada. La idea es no tener que escribir manualmente ninguna prueba; ni implementar interfaces para burlarse de las respuestas del servidor.

Víctor Albertos
fuente
7
  1. Primero, cree su interfaz 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. Su Solicitante a continuación:

    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 usa la segunda opción (use la interfaz de Retrofit para simular los datos del servidor), necesita MockRetrofit, use el siguiente código:

    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.Mis datos son del archivo de activos (Asset / server / EventList.json), el contenido de este archivo es:

    {
      "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 está utilizando el interceptor okhttp3, necesita un interceptor autodefinido, como este:

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.Finalmente, puede solicitar su servidor con código:

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

Gracias por leer.

Lai ZuLing
fuente
5

Agregando a la respuesta de @Alec, he extendido el cliente simulado para obtener la respuesta directamente desde un archivo de texto en la carpeta de activos, según la URL de la solicitud.

Ex

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

Aquí, el cliente simulado entiende que la URL que se está disparando está activada y busca un archivo llamado active.txt en la carpeta de activos. Lee el contenido del archivo assets / active.txt y lo envía como respuesta a la API.

Aquí está el extendido 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()));
    }
}

Para obtener una explicación detallada, puede consultar mi blog
http://www.cumulations.com/blogs/13/Mock-API-response-in-Retrofit-using-custom-clients

praveena_kd
fuente
hola, cuando estoy escribiendo una clase de prueba usando robolectric y usando un cliente simulado para simular la API de actualización, no me está dando ninguna respuesta. ¿Podrías guiarme sobre cómo hacer esto?
Dory
Hola @Dory, asegúrate de tener la parte final de la URL y el nombre del archivo dentro de la carpeta de activos. Por ejemplo, digamos que su URL es la siguiente (usando Reftrofit aquí) @POST ("/ redeemGyft") public void redeemGyft (@Body MposRequest reqdata, Callback <RedeemGyftResponse> callback); entonces el nombre del archivo correspondiente en la carpeta de activos es redeemgyft.txt
praveena_kd
Le di un nombre de archivo estático, en mi MockClientarchivo, escribí una clase de prueba usando robolectric. Pero no puedo obtener ninguna respuesta del archivo json.
Dory
si ha guardado el archivo dentro de la carpeta de activos, debería recogerlo.
praveena_kd
1

JSONPlaceholder: API REST en línea falsa para pruebas y creación de prototipos

https://jsonplaceholder.typicode.com/

ReqresIn: otra API REST en línea

https://reqres.in/

Servidor simulado de cartero

Si desea probar la carga útil de respuesta personalizada, es posible que los dos anteriores no se ajusten a sus requisitos, entonces puede probar el servidor simulado de cartero. Es bastante fácil de configurar y flexible para definir su propia carga útil de solicitud y respuesta.

ingrese la descripción de la imagen aquí https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/ https://youtu.be/shYn3Ys3ygE

li2
fuente
1

Simular llamadas de API con Retrofit ahora es aún más fácil con Mockinizer, que hace que trabajar con MockWebServer sea realmente sencillo:

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

)

Simplemente cree un mapa de RequestFilter y MockResponses y luego conéctelo a su cadena de construcción OkHttpClient:

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

No tiene que preocuparse por configurar MockWebServer, etc. Simplemente agregue sus simulacros, el resto lo hace Mockinizer por usted.

(Descargo de responsabilidad: soy el autor de Mockinizer)

donfuxx
fuente
0

Para mí, el cliente de reacondicionamiento personalizado es excelente debido a su flexibilidad. Especialmente cuando usa cualquier marco DI, puede activar / desactivar simulacros de forma rápida y sencilla. Estoy usando un cliente personalizado proporcionado por Dagger también en pruebas unitarias y de integración.

Editar: Aquí puede encontrar un ejemplo de modificación burlona https://github.com/pawelByszewski/retrofitmock

Paweł Byszewski
fuente