Simulação de servidor de retrofit quadrado para teste

97

Qual é a melhor maneira de simular um servidor para teste ao usar a estrutura quadrada de retrofit .

Formas potenciais:

  1. Crie um novo cliente de retrofit e configure-o no RestAdapter.Builder (). SetClient (). Isso envolve a análise do objeto Request e o retorno do json como um objeto Response.

  2. Implemente esta interface anotada como uma classe simulada e use-a no lugar da versão fornecida por RestAdapter.create () (não testará a serialização gson)

  3. ?

O ideal é que o servidor simulado forneça respostas json para que possa testar a serialização gson ao mesmo tempo.

Quaisquer exemplos seriam muito apreciados.

Alec Holmes
fonte
@JakeWharton, qual é o propósito square-oss? Parece redundante dado retrofit.
Charles,
@Alec Holmes: Você resolveu seu problema?
AndiGeeky

Respostas:

104

Solicitações de simulação de retrofit 2.0 para teste

Como os mecanismos antigos, como criar MockClientclasses e implementá-las, Clientnão estão mais funcionando com o Retrofit 2.0, aqui eu descrevo uma nova maneira de fazer isso. Tudo o que você precisa fazer agora é adicionar seus interceptores personalizados para OkHttpClient como mostrado abaixo . FakeInterceptora classe apenas substitui o interceptmétodo e, no caso, se o aplicativo estiver no DEBUGmodo, retorne o JSON fornecido.

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 fonte do projeto no GitHub

Victor Apoyan
fonte
9
Para evitar UnsupportedOperationException, use OkHttpClient.Builder. final OkHttpClient okHttpClient = new OkHttpClient.Builder () .addInterceptor (new FakeInterceptor ()) .build ();
João
4
Dois problemas que eu tenho: 1- Não há nenhum uri()under chain.request().uri()(eu consertei isso String url = chain.request().url().toString();porque meu caso é diferente). 2- Estou conseguindo java.lang.IllegalStateException: network interceptor my.package.name.FakeInterceptor must call proceed() exactly once. Eu adicionei isso em addNetworkInterceptor()vez de addInterceptor().
Hesam
2
use chain.request (). url (). uri ();
Amol Gupta de
Como posso simular o erro 401 para testar o método httpClient.authenticator? apenas colocando o código "401" método de autenticação não chama. como posso lidar com isso?
Mahdi
Eu levei a abordagem do interceptor falso para simular apis da web para o próximo nível e publiquei uma pequena biblioteca para tornar ainda mais fácil e conveniente. Consulte github.com/donfuxx/Mockinizer
donfuxx
85

Decidi tentar o método 1 da seguinte maneira

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 usando-o por:

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

Ele funciona bem e permite que você teste suas strings json sem ter que entrar em contato com o servidor real!

Alec Holmes
fonte
Eu atualizei o construtor Response usado porque o antigo estava obsoleto, que lançava um IllegalArgumentException url == nullcom o Retrofit 1.4.1.
Dan J
1
Também é necessário adicionar um endpoint ao construtor:builder.setEndpoint("http://mockserver.com").setClient(new MockClient());
codeprogression,
Estendi o cliente simulado acima para buscar a resposta de um arquivo na pasta de ativos, dependendo da solicitação de URL.
praveena_kd
21
O Retrofit 2 agora usa OkHttpClient para a camada do cliente e este código não funciona. ¿Alguma ideia de como fazer um mock OkHttpClient? Provavelmente, trata-se de estendê-lo e substituí-lo, mas não sei como.
GuillermoMP de
1
Você pode atualizar sua resposta com base no Retrofit2 também? obrigado
Hesam
20

Testar a desserialização JSON para seus objetos (presumivelmente com TypeAdapters?) Parece um problema separado que requer testes de unidade separados.

Eu uso a versão 2 pessoalmente. Ele oferece código seguro para tipos e fácil de refatorar que pode ser facilmente depurado e alterado. Afinal, de que adianta declarar sua API como interfaces se você não está criando versões alternativas delas para teste! Polimorfismo para a vitória.

Outra opção é usar um Java Proxy. É assim que o Retrofit (atualmente) implementa sua interação HTTP subjacente. Isso certamente exigirá mais trabalho, mas permitiria simulações muito mais dinâmicas.

Jake Wharton
fonte
Esta também é a minha forma preferida. É muito mais simples depurar conforme declarado acima do que lidar diretamente com o corpo de resposta. @alec Se você quiser testar a serialização GSON, gere / leia uma string json e use um objeto gson para desserializar. Sob a cabeça, acredito que seja o que o Retrofit faz.
loeschg
@JakeWharton Você poderia dar um pequeno exemplo do que isso gostaria? Estou tendo problemas para visualizar isso ... Obrigado!
uncle_tex
1
@uncle_tex Dê uma olhada em github.com/JakeWharton/u2020/blob/master/src/internalDebug/java/…
riwnodennyk
8

Eu sou um grande fã do Apiary.io para uma simulação de uma API antes de mudar para um servidor real.

Você também pode usar arquivos .json simples e lê-los do sistema de arquivos.

Você também pode usar APIs de acesso público, como Twitter, Flickr, etc.

Aqui estão alguns outros excelentes recursos sobre Retrofit.

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

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

Projeto de exemplo: https://github.com/dustin-graham/ucad_twitter_retrofit_sample

jpotts18
fonte
7

Zombaria (aviso: eu sou o autor) foi projetado exatamente para essa tarefa.

Mockery é uma biblioteca de simulação / teste focada na validação de camadas de rede com suporte integrado para Retrofit. Ele gera automaticamente testes JUnit com base nas especificações de um determinado Api. A ideia é não ter que escrever manualmente nenhum teste; nem implementar interfaces para simular respostas do servidor.

Víctor Albertos
fonte
7
  1. Primeiro, crie sua interface de 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. Seu solicitante a seguir:

    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 você estiver usando a segunda escolha (use a interface de Retrofit para dados do servidor Mock), você precisa MockRetrofit, use o código a seguir:

    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. Meus dados são do arquivo de ativos (Asset / server / EventList.json), o conteúdo deste arquivo é:

    {
      "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 você estiver usando o interceptor okhttp3, você precisa se auto-definir o interceptor, 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. Por fim, você pode solicitar seu servidor com o 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!!!
    });
}

Obrigado pela leitura.

Lai ZuLing
fonte
5

Adicionando à resposta de @Alec, estendi o cliente simulado para obter a resposta diretamente de um arquivo de texto na pasta de ativos, dependendo da URL de solicitação.

Ex

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

Aqui, o cliente simulado entende que a URL que está sendo disparada está ativa e procura um arquivo chamado activate.txt na pasta de ativos. Ele lê o conteúdo do arquivo assets / activate.txt e o envia como resposta para a API.

Aqui está o estendido 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 obter uma explicação detalhada, você pode conferir meu blog
http://www.cumulations.com/blogs/13/Mock-API-response-in-Retrofit-using-custom-clients

praveena_kd
fonte
oi, Quando estou escrevendo uma classe de teste usando robolectric e usando cliente simulado para simular a api de retrofit, ele não está me dando nenhuma resposta. Você poderia me orientar como fazer isso.
Dory de
Olá @Dory, certifique-se de ter a parte final do URL e o nome do arquivo dentro da pasta de ativos. Por exemplo, digamos que sua URL seja a seguinte (usando Reftrofit aqui) @POST ("/ redeemGyft") public void redeemGyft (@Body MposRequest reqdata, Callback <RedeemGyftResponse> callback); então, o nome do arquivo correspondente na pasta de ativos é redeemgyft.txt
praveena_kd
Eu dei um nome de arquivo estático, em meu MockClientarquivo, escrevi uma classe de teste usando robolectric. Mas não consigo obter nenhuma resposta do arquivo json.
Dory de
se você manteve o arquivo dentro da pasta de ativos, ele deve pegá-lo.
praveena_kd
1

JSONPlaceholder: API REST online falsa para teste e prototipagem

https://jsonplaceholder.typicode.com/

ReqresIn: outra API REST online

https://reqres.in/

Servidor simulado Postman

Se você quiser testar a carga útil de resposta personalizada, os dois acima podem não atender às suas necessidades, então você pode tentar o servidor simulado do postman. É muito fácil de configurar e flexível para definir sua própria solicitação e carga útil de resposta.

insira a descrição da imagem aqui https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/ https://youtu.be/shYn3Ys3ygE

li2
fonte
1

Zombar de chamadas de API com o Retrofit agora é ainda mais fácil com o Mockinizer, que torna o trabalho com o MockWebServer realmente simples:

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 criar um mapa de RequestFilter e MockResponses e, em seguida, conectá-lo à cadeia de construtores OkHttpClient:

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

Você não precisa se preocupar em configurar o MockWebServer etc. Basta adicionar seus mocks, todo o resto é feito pelo Mockinizer para você.

(Isenção de responsabilidade: eu sou o autor do Mockinizer)

Donfuxx
fonte
0

Para mim, o cliente de retrofit personalizado é ótimo por causa da flexibilidade. Especialmente quando você usa qualquer framework de DI, você pode ligar / desligar o mock de maneira rápida e simples. Estou usando o cliente personalizado fornecido pela Dagger também em testes de unidade e integração.

Edit: Aqui você encontra um exemplo de retrofit de simulação https://github.com/pawelByszewski/retrofitmock

Paweł Byszewski
fonte