Tiruan server retrofit persegi untuk pengujian

97

Apa cara terbaik untuk memalsukan server untuk pengujian saat menggunakan kerangka retrofit persegi .

Cara potensial:

  1. Buat klien retrofit baru dan setel di RestAdapter.Builder (). SetClient (). Ini melibatkan penguraian objek Request dan mengembalikan json sebagai objek Respon.

  2. Implementasikan antarmuka beranotasi ini sebagai kelas tiruan dan gunakan itu sebagai pengganti versi yang disediakan oleh RestAdapter.create () (tidak akan menguji serialisasi gson)

  3. ?

Idealnya saya ingin server tiruan memberikan tanggapan json sehingga saya dapat menguji serialisasi gson pada saat yang bersamaan.

Setiap contoh akan sangat dihargai.

Alec Holmes
sumber
@JakeWharton, apa tujuan dari square-oss? Tampaknya diberikan berlebihan retrofit.
Charles
@ Alec Holmes: Apakah Anda memecahkan masalah Anda?
AndiGeeky

Jawaban:

104

Permintaan Mock Retrofit 2.0 untuk Pengujian

Karena mekanisme lama seperti membuat MockClientkelas dan mengimplementasikannya Clienttidak berfungsi lagi dengan Retrofit 2.0, di sini saya menjelaskan cara baru untuk melakukan itu. Semua yang perlu Anda lakukan sekarang adalah menambahkan interseptor kustom Anda untuk OkHttpClient seperti yang ditunjukkan di bawah ini . FakeInterceptorclass hanya mengganti interceptmetode dan dalam kasus jika aplikasi dalam DEBUGmode kembali diberikan 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;
    }
}

Kode sumber proyek di GitHub

Victor Apoyan
sumber
9
Untuk menghindari UnsupportedOperationException, gunakan OkHttpClient.Builder. akhir OkHttpClient okHttpClient = baru OkHttpClient.Builder () .addInterceptor (baru FakeInterceptor ()) .build ();
Yohanes
4
Dua masalah yang saya miliki: 1- Tidak ada uri()kekurangan chain.request().uri()(saya memperbaikinya String url = chain.request().url().toString();karena kasus saya berbeda). 2- Saya mendapatkan java.lang.IllegalStateException: network interceptor my.package.name.FakeInterceptor must call proceed() exactly once. Saya telah menambahkan ini ke addNetworkInterceptor()daripada addInterceptor().
Hesam
2
gunakan chain.request (). url (). uri ();
Amol Gupta
Bagaimana saya bisa meniru kesalahan 401 untuk menguji metode httpClient.authenticator? dengan hanya meletakkan kode "401" metode otentikasi tidak panggilan. bagaimana saya bisa menangani ini?
Mahdi
Saya telah mengambil pendekatan interseptor palsu untuk mengejek apis web ke tingkat berikutnya dan menerbitkan perpustakaan kecil untuk membuatnya lebih mudah dan nyaman. Lihat github.com/donfuxx/Mockinizer
donfuxx
85

Saya memutuskan untuk mencoba metode 1 sebagai berikut

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

Dan menggunakannya dengan:

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

Ini berfungsi dengan baik dan memungkinkan Anda untuk menguji string json Anda tanpa harus menghubungi server sebenarnya!

Alec Holmes
sumber
Saya telah memperbarui konstruktor Respons yang digunakan karena yang lama sudah tidak digunakan lagi, yang IllegalArgumentException url == nullmenggunakan Retrofit 1.4.1.
Dan J
1
Juga perlu menambahkan titik akhir ke pembangun:builder.setEndpoint("http://mockserver.com").setClient(new MockClient());
codeprogress
Saya telah memperpanjang klien tiruan di atas untuk mengambil respons dari file di folder aset tergantung pada permintaan URL.
praveena_kd
21
Retrofit 2 sekarang menggunakan OkHttpClient untuk lapisan klien, dan kode ini tidak berfungsi. ¿Ada ide bagaimana membuat tiruan OkHttpClient? Mungkin ini semua tentang memperpanjang dan menimpanya, tapi saya tidak yakin bagaimana caranya.
GuillermoMP
1
Bisakah Anda memperbarui jawaban Anda berdasarkan Retrofit2 juga? terima kasih
Hesam
20

Menguji deserialisasi JSON ke objek Anda (mungkin dengan TypeAdapters?) Tampaknya seperti masalah terpisah yang memerlukan pengujian unit terpisah.

Saya menggunakan versi 2 secara pribadi. Ini memberikan kode yang aman untuk tipe, ramah refactor yang dapat dengan mudah di-debug dan diubah. Lagi pula, apa gunanya mendeklarasikan API Anda sebagai antarmuka jika Anda tidak membuat versi alternatif untuk pengujian! Polimorfisme untuk kemenangan.

Pilihan lainnya adalah menggunakan Java Proxy. Ini sebenarnya bagaimana Retrofit (saat ini) mengimplementasikan interaksi HTTP yang mendasarinya. Ini memang membutuhkan lebih banyak pekerjaan, tetapi akan memungkinkan tiruan yang jauh lebih dinamis.

Jake Wharton
sumber
Ini juga cara yang saya sukai. Jauh lebih mudah untuk men-debug seperti yang dinyatakan di atas daripada harus berurusan langsung dengan badan respons. @alec Jika Anda ingin menguji serialisasi GSON, buat / baca dalam string json dan gunakan objek gson untuk deserialisasi. Di bawah kepala saya percaya itulah yang dilakukan Retrofit.
loeschg
@JakeWharton Bisakah Anda memberikan contoh singkat tentang seperti apa ini? Saya kesulitan memvisualisasikan ini ... Terima kasih!
paman_teks
8

Saya adalah penggemar berat Apiary.io karena mengejek API sebelum pindah ke server sebenarnya.

Anda juga dapat menggunakan file .json datar dan membacanya dari sistem file.

Anda juga dapat menggunakan API yang dapat diakses publik seperti Twitter, Flickr, dll.

Berikut adalah beberapa sumber bagus lainnya tentang Retrofit.

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

Contoh Proyek: https://github.com/dustin-graham/ucad_twitter_retrofit_sample

jpotts18
sumber
7

Ejekan (penafian: Akulah penulisnya) dirancang hanya untuk tugas ini.

Mockery adalah pustaka tiruan / pengujian yang berfokus pada memvalidasi lapisan jaringan dengan dukungan bawaan untuk Retrofit. Ini menghasilkan pengujian JUnit secara otomatis berdasarkan spesifikasi Api yang diberikan. Idenya adalah untuk tidak menulis tes apapun secara manual; tidak mengimplementasikan antarmuka untuk meniru respons server.

Víctor Albertos
sumber
7
  1. Pertama, buat antarmuka Retrofit Anda.

    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. Pemohon Anda mengikuti:

    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. Jika Anda menggunakan pilihan kedua (gunakan antarmuka Retrofit ke data server Mock), Anda perlu MockRetrofit, gunakan kode berikut:

    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. Data saya berasal dari file aset (Asset / server / EventList.json), konten file ini adalah:

    {
      "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. Jika Anda menggunakan interseptor okhttp3, Anda perlu interseptor yang ditentukan sendiri, seperti ini:

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. Terakhir, Anda dapat meminta server Anda dengan kode:

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

Terima kasih sudah membaca.

Lai ZuLing
sumber
5

Menambah jawaban oleh @Alec, saya telah memperluas klien tiruan untuk mendapatkan respons langsung dari file teks di folder aset tergantung pada URL permintaan.

Ex

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

Di sini klien tiruan, memahami bahwa URL yang diaktifkan diaktifkan dan mencari file bernama activ.txt di folder aset. Itu membaca konten dari file assets / activ.txt dan mengirimkannya sebagai respons untuk API.

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

Untuk penjelasan rinci, Anda dapat memeriksa blog saya
http://www.cumulations.com/blogs/13/Mock-API-response-in-Retrofit-using-custom-clients

praveena_kd
sumber
hai, Ketika saya menulis kelas tes menggunakan robolectric dan menggunakan klien tiruan untuk meniru api retrofit, itu tidak memberi saya tanggapan apa pun. Bisakah Anda membimbing saya bagaimana melakukan ini.
Dory
Hai @Dory, pastikan Anda memiliki bagian akhir URL dan nama file di dalam folder aset. Misalnya, URL Anda adalah seperti di bawah ini (menggunakan Reftrofit di sini) @POST ("/ redeemGyft") public void redeemGyft (@Body MposRequest reqdata, Callback <RedeemGyftResponse> callback); maka nama file yang sesuai dalam folder aset adalah redeemgyft.txt
praveena_kd
Saya telah memberikan nama file statis, dalam MockClientfile saya , menulis kelas uji menggunakan robolectric. Tetapi saya tidak bisa mendapatkan tanggapan apa pun dari file json.
Dory
jika Anda menyimpan file di dalam folder aset, file tersebut harus diambil.
praveena_kd
1

JSONPlaceholder: REST API Online Palsu untuk Pengujian dan Pembuatan Prototipe

https://jsonplaceholder.typicode.com/

ReqresIn: REST API Online Lainnya

https://reqres.in/

Server tiruan tukang pos

Jika Anda ingin menguji muatan respons yang disesuaikan, dua di atas mungkin tidak sesuai dengan kebutuhan Anda, maka Anda dapat mencoba server tiruan tukang pos. Cukup mudah disiapkan dan fleksibel untuk menentukan permintaan Anda sendiri dan muatan respons.

masukkan deskripsi gambar di sini https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/ https://youtu.be/shYn3Ys3ygE

li2
sumber
1

Mocking api panggilan dengan Retrofit sekarang lebih mudah dengan Mockinizer yang membuat bekerja dengan MockWebServer sangat mudah:

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

)

Cukup buat peta RequestFilter dan MockResponses dan kemudian hubungkan ke rantai pembuat OkHttpClient Anda:

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

Anda tidak perlu khawatir tentang mengkonfigurasi MockWebServer dll. Cukup tambahkan ejekan Anda, sisanya dilakukan oleh Mockinizer untuk Anda.

(Penafian: Saya adalah penulis Mockinizer)

donfuxx
sumber
0

Bagi saya, Klien Retrofit khusus sangat bagus karena fleksibilitas. Terutama ketika Anda menggunakan kerangka DI apa pun, Anda dapat mengaktifkan / menonaktifkan tiruan dengan cepat dan sederhana. Saya menggunakan Klien khusus yang disediakan oleh Dagger juga dalam pengujian unit dan integrasi.

Sunting: Di sini Anda menemukan contoh mengejek retrofit https://github.com/pawelByszewski/retrofitmock

Paweł Byszewski
sumber