Квадратний макет сервера для модернізації для тестування


97

Що найкращий спосіб знущатися над сервером для тестування при використанні квадратної рамки для модернізації .

Потенційні шляхи:

  1. Створіть нового клієнта модернізації та встановіть його в RestAdapter.Builder (). SetClient (). Це передбачає розбір об'єкта Запит та повернення json як об’єкт відповіді.

  2. Реалізуйте цей анотований інтерфейс як макетний клас та використовуйте його замість версії, наданої RestAdapter.create () (звичайна серіалізація gson test)

  3. ?

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

Будь-які приклади були б дуже вдячні.


@JakeWharton, в чому мета square-oss? Це здається зайвим retrofit.
Чарльз

@Alec Holmes: Ви вирішили свою проблему?
AndiGeeky

Відповіді:


104

Запрошення на модернізацію 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


9
Щоб уникнути UnsupportedOperationException використовуйте OkHttpClient.Builder. остаточний 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().
Гесам

2
використовувати chain.request (). url (). uri ();
Амол Гупта,

Як я можу знущатися над помилкою 401 для тестування методу httpClient.authenticator? просто поставивши код "401", метод аутентифікації не викликає. як я можу з цим впоратися?
Махді

Я застосував підроблений підхоплюючий підхід до знущань над веб-апісами на наступний рівень і опублікував для цього трохи бібліотеки, щоб зробити це ще простішим та зручнішим. Дивіться 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.
Dan J

1
Також потрібно додати кінцеву точку до builder.setEndpoint("http://mockserver.com").setClient(new MockClient());
конструктора

Я розширив макет-клієнт вище, щоб отримати відповідь із файлу в папці активів, залежно від запиту URL.
praveena_kd

21
Модернізація 2 тепер використовує OkHttpClient для клієнтського рівня, і цей код не працює. Будь-яка ідея, як зробити макет OkHttpClient? Напевно, справа в тому, щоб розширити і змінити це, але я не знаю як.
GuillermoMP

1
Чи можете ви також оновити свою відповідь на основі Retrofit2? спасибі
Гесам

20

Тестування десеріалізації JSON для ваших об'єктів (імовірно за допомогою TypeAdapters ?) Представляється окремою проблемою, яка вимагає окремих модульних тестів.

Я використовую версію 2 особисто. Він надає безпечний для типу код, сприятливий для рефакторів, який можна легко налагодити та змінити. Зрештою, яка користь від оголошення вашого API як інтерфейсу, якщо ви не створюєте їх альтернативні версії для тестування! Поліморфізм для перемоги.

Інший варіант - використання Java Proxy. Це фактично те, як Retrofit (на даний момент) реалізує свою базову взаємодію HTTP. Це, безумовно, вимагатиме більше роботи, але дозволить зробити набагато більш динамічні знущання.


Це теж мій улюблений спосіб. Набагато простіше налагодити, як зазначено вище, ніж мати справу безпосередньо з тілом відповіді. @alec Якщо ви хочете перевірити серіалізацію GSON, згенеруйте / прочитайте рядок json і використовуйте об'єкт gson для десеріалізації. Під головою я вважаю, що саме це робить Retrofit.
loeschg

@JakeWharton Не могли б ви навести короткий приклад того, чого б ви хотіли? У мене виникають проблеми з візуалізацією цього ... Дякую!
uncle_tex



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


7

Знущання (застереження: я автор) було розроблено саме для цього завдання.

Знущання - це бібліотека для знущань / тестувань, орієнтована на перевірку мережевих шарів із вбудованою підтримкою модернізації. Він автоматично генерує тести JUnit на основі специфікацій даного Api. Ідея полягає не в тому, щоб писати будь-який тест вручну; ні реалізація інтерфейсів для знущань над реакціями сервера.


7
  1. Спочатку створіть інтерфейс 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. Ваш запитувач:

    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. Якщо ви використовуєте другий вибір (використовуйте інтерфейс модернізації для даних 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-адреси запиту.

Вих

@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


привіт, коли я пишу тестовий клас за допомогою robolectric та використовую макет-клієнт для знущань над модернізованим api, це не дає мені жодної відповіді. Не могли б ви навести мене, як це зробити.
Дорі

Привіт @Dory, переконайтеся, що у вас є частина, що закінчується URL-адресою, та ім’я файлу всередині папки „ресурси”. Наприклад, скажімо, що ваша URL-адреса наведена нижче (використовуючи Reftrofit тут) @POST ("/ redeemGyft") public void redeemGyft (reqdata @Body MposRequest, зворотний виклик <RedeemGyftResponse> зворотний виклик); то ім'я файлу correspodning у папці активів буде redeemgyft.txt
praveena_kd

Я дав статичне ім’я файлу, у своєму MockClientфайлі написав тестовий клас, використовуючи робочелектрик. Але я не в змозі отримати жодної відповіді з файлу json.
Дорі

якщо ви зберегли файл у папці активів, його слід забрати.
praveena_kd

1

JSONPlaceholder: підроблений Інтернет REST API для тестування та прототипування

https://jsonplaceholder.typicode.com/

ReqresIn: Ще один Інтернет-REST API

https://reqres.in/

Сервер макетів листоноші

Якщо ви хочете перевірити корисну навантаження на відповідь, вищевказані два можуть не відповідати вашим вимогам, тоді ви можете спробувати сервер макетів листоноші. Це досить просто в налаштуванні та гнучкості для визначення власного запиту та корисного навантаження.

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


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)


0

Для мене спеціальний Клієнт модернізації чудовий завдяки гнучкості. Особливо, коли ви використовуєте будь-яку структуру DI, ви можете швидко та просто вмикати / вимикати макет. Я використовую користувацький клієнт, який надає Dagger, також для модульних та інтеграційних тестів.

Редагувати: Тут ви знайдете приклад знущання над модернізацією https://github.com/pawelByszewski/retrofitmock

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.