Квадратный модернизированный сервер для тестирования

97

Как лучше всего смоделировать сервер для тестирования при использовании квадратного фреймворка .

Возможные способы:

  1. Создайте нового клиента модернизации и установите его в RestAdapter.Builder (). SetClient (). Это включает в себя синтаксический анализ объекта Request и возврат json в качестве объекта Response.

  2. Реализуйте этот аннотированный интерфейс как фиктивный класс и используйте его вместо версии, предоставленной RestAdapter.create () (не тестируйте сериализацию gson)

  3. ?

В идеале я хочу, чтобы имитируемый сервер предоставлял ответы json, чтобы я мог одновременно протестировать сериализацию gson.

Приветствуются любые примеры.

Алек Холмс
источник
@JakeWharton, в чем цель square-oss? Это кажется лишним retrofit.
Чарльз
@Alec Holmes: Вы решили свою проблему?
AndiGeeky

Ответы:

104

Mock Retrofit 2.0: запросы на тестирование

Поскольку старые механизмы, такие как создание MockClientкласса и его реализация Client, больше не работают с Retrofit 2.0, здесь я описываю новый способ сделать это. Все, что вам нужно сделать сейчас, это добавить свои собственные перехватчики для OkHttpClient, как показано ниже . FakeInterceptorкласс просто переопределяет interceptметод и в случае, если приложение находится в DEBUGрежиме, возвращает данный 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;
    }
}

Исходный код проекта на GitHub

Виктор Апоян
источник
10
Чтобы избежать исключения UnsupportedOperationException, используйте OkHttpClient.Builder. final OkHttpClient okHttpClient = новый OkHttpClient.Builder () .addInterceptor (новый FakeInterceptor ()) .build ();
Джон
4
У меня есть две проблемы: 1- Нет uri()недостатка chain.request().uri()(я исправил это, так String url = chain.request().url().toString();как мой случай отличается). 2- Я получаю java.lang.IllegalStateException: network interceptor my.package.name.FakeInterceptor must call proceed() exactly once. Я добавил это addNetworkInterceptor()вместо addInterceptor().
Hesam
2
используйте chain.request (). url (). uri ();
Амол Гупта
Как я могу имитировать ошибку 401 для проверки метода httpClient.authenticator? просто поместив код «401», метод аутентификации не вызывает. как я могу справиться с этим?
Махди
Я поднял фальшивый перехватчик для имитации веб-API на новый уровень и опубликовал небольшую библиотеку для этого, чтобы сделать его еще проще и удобнее. См github.com/donfuxx/Mockinizer
donfuxx
85

Я решил попробовать способ 1 следующим образом

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

И используя его:

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

Он работает хорошо и позволяет вам тестировать ваши json-строки без необходимости связываться с реальным сервером!

Алек Холмс
источник
Я обновил конструктор Response, который использовался, поскольку старый был устаревшим, который IllegalArgumentException url == nullвызывал ошибку с помощью Retrofit 1.4.1.
Дэн Дж,
1
Также необходимо добавить конечную точку в конструктор:builder.setEndpoint("http://mockserver.com").setClient(new MockClient());
codeprogression
Я расширил макет клиента выше, чтобы получить ответ из файла в папке ресурсов в зависимости от URL-запроса.
praveena_kd 06
21
Retrofit 2 теперь использует OkHttpClient для клиентского уровня, и этот код не работает. ¿Есть идеи, как сделать макет OkHttpClient? Вероятно, все дело в его расширении и отмене, но я не уверен, как это сделать.
GuillermoMP
1
Можете ли вы обновить свой ответ на основе Retrofit2? спасибо
Hesam 07
20

Тестирование десериализации JSON для ваших объектов (предположительно с помощью TypeAdapters?) Кажется отдельной проблемой, требующей отдельных модульных тестов.

Лично пользуюсь версией 2. Он предоставляет типобезопасный, удобный для рефакторинга код, который можно легко отлаживать и изменять. В конце концов, какой толк в объявлении вашего API интерфейсами, если вы не создаете их альтернативные версии для тестирования! Полиморфизм для победы.

Другой вариант - использовать Java Proxy. Фактически, Retrofit (в настоящее время) реализует свое базовое HTTP-взаимодействие. Это, по общему признанию, потребует больше работы, но позволит создать гораздо более динамичные макеты.

Джейк Уортон
источник
Я тоже предпочитаю этот способ. Намного проще отлаживать, как указано выше, чем иметь дело непосредственно с телом ответа. @alec Если вы хотите протестировать сериализацию GSON, сгенерируйте / считайте строку json и используйте объект gson для десериализации. Под головой я считаю, что Retrofit в любом случае делает это.
Loeschg
@JakeWharton Не могли бы вы привести небольшой пример того, что вам нужно? У меня проблемы с визуализацией этого ... Спасибо!
uncle_tex 02
1
@uncle_tex Загляните на github.com/JakeWharton/u2020/blob/master/src/internalDebug/java/…
riwnodennyk
8

Я большой поклонник Apiary.io за насмешку над API перед переходом на настоящий сервер.

Вы также можете использовать плоские файлы .json и читать их из файловой системы.

Вы также можете использовать общедоступные API, такие как Twitter, Flickr и т. Д.

Вот еще несколько замечательных ресурсов о модернизации.

Слайды: https://docs.google.com/presentation/d/12Eb8OPI0PDisCjWne9-0qlXvp_-R4HmqVCjigOIgwfY/edit#slide=id.p

Видео: http://www.youtube.com/watch?v=UtM06W51pPw&feature=g-user-u

Пример проекта: https://github.com/dustin-graham/ucad_twitter_retrofit_sample

jpotts18
источник
7

Издевательство (отказ от ответственности: я автор) было разработано как раз для этой задачи.

Mockery - это библиотека имитации / тестирования, ориентированная на проверку сетевых уровней со встроенной поддержкой Retrofit. Он автоматически генерирует тесты JUnit на основе спецификаций данного Api. Идея состоит в том, чтобы не писать вручную какой-либо тест; ни реализация интерфейсов для имитации ответов сервера.

Виктор Альбертос
источник
7
  1. Сначала создайте интерфейс для модернизации.

    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. Ваш запросчик в следующем:

    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. Если вы используете второй вариант (используйте интерфейс Retrofit для данных Mock-сервера), вам необходимо MockRetrofit, используйте следующий код:

    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. Мои данные взяты из файла актива (Asset / server / EventList.json), содержимое этого файла:

    {
      "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.Если вы используете перехватчик okhttp3, вам потребуется самоопределенный перехватчик, например:

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. Наконец, вы можете запросить свой сервер с помощью кода:

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

Спасибо за прочтение.

Лай Цзулин
источник
5

Добавляя к ответу @Alec, я расширил фиктивный клиент, чтобы получать ответ непосредственно из текстового файла в папке с ресурсами в зависимости от URL-адреса запроса.

Ex

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

Здесь макет-клиент понимает, что активирован URL-адрес, и ищет файл с именем activate.txt в папке с ресурсами. Он считывает содержимое из файла assets / activate.txt и отправляет его в качестве ответа API.

Вот расширенный 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()));
    }
}

Для подробного объяснения вы можете проверить мой блог
http://www.cumulations.com/blogs/13/Mock-API-response-in-Retrofit-using-custom-clients

praveena_kd
источник
привет, когда я пишу тестовый класс с использованием robolectric и использую макет клиента для имитации модифицированного api, он не дает мне никакого ответа. Не могли бы вы посоветовать мне, как это сделать.
Дори
Привет, @Dory, убедитесь, что у вас есть конечная часть URL-адреса и имя файла в папке с ресурсами. Например, допустим, ваш URL-адрес выглядит следующим образом (здесь используется Reftrofit) @POST ("/ redeemGyft") public void redeemGyft (@Body MposRequest reqdata, обратный вызов <RedeemGyftResponse> обратный вызов); тогда соответствующее имя файла в папке с ресурсами
redeemgyft.txt
Я дал статическое имя файла, в моем MockClientфайле написал тестовый класс с использованием robolectric. Но я не могу получить ответ от файла json.
Дори
если вы сохранили файл в папке с ресурсами, он должен его забрать.
praveena_kd
1

JSONPlaceholder: поддельный онлайн-API REST для тестирования и прототипирования

https://jsonplaceholder.typicode.com/

ReqresIn: еще один онлайн-API REST

https://reqres.in/

Почтальон макет сервера

Если вы хотите протестировать настраиваемую полезную нагрузку ответа, два приведенных выше могут не соответствовать вашим требованиям, тогда вы можете попробовать почтовый сервер макета. Его довольно легко настроить, и он позволяет гибко определять свои собственные данные запроса и ответа.

введите описание изображения здесь https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/ https://youtu.be/shYn3Ys3ygE

li2
источник
1

Мокинг вызовов API с помощью Retrofit теперь еще проще с Mockinizer, что делает работу с MockWebServer действительно простой:

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

)

Просто создайте карту RequestFilter и MockResponses, а затем подключите ее к своей цепочке построителей OkHttpClient:

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

Вам не нужно беспокоиться о настройке MockWebServer и т. Д. Просто добавьте свои макеты, все остальное Mockinizer сделает за вас.

(Отказ от ответственности: я являюсь автором Mockinizer)

Donfuxx
источник
0

Для меня пользовательский Retrofit Client великолепен своей гибкостью. Особенно, когда вы используете любую структуру DI, вы можете быстро и просто включить / выключить макет. Я использую собственный клиент, предоставленный Dagger, также в модульных и интеграционных тестах.

Изменить: здесь вы найдете пример имитации модернизации https://github.com/pawelByszewski/retrofitmock

Павел Бышевский
источник