Test için kare güçlendirme sunucusu taklidi


98

Kare güçlendirme çerçevesini kullanırken test için bir sunucuyla dalga geçmenin en iyi yolu nedir ?

Olası yollar:

  1. Yeni bir retrofit istemcisi oluşturun ve RestAdapter.Builder (). SetClient () içinde ayarlayın. Bu, Request nesnesinin ayrıştırılmasını ve json'un bir Response nesnesi olarak döndürülmesini içerir.

  2. Bu ek açıklamalı arayüzü bir sahte sınıf olarak uygulayın ve bunu RestAdapter.create () tarafından sağlanan sürümün yerine kullanın (gson serileştirmeyi test etmeyecek)

  3. ?

İdeal olarak, taklit edilen sunucunun json yanıtları sağlamasını istiyorum, böylece gson serileştirmeyi aynı anda test edebilirim.

Herhangi bir örnek çok takdir edilecektir.


@JakeWharton, amacı square-ossnedir? Gereksiz görünüyor retrofit.
Charles

@Alec Holmes: Problemini çözdün mü?
AndiGeeky

Yanıtlar:


107

Mock Retrofit 2.0 Test için İstekleri

MockClientSınıf yaratma ve onu uygulama gibi eski mekanizmalar Clientartık Retrofit 2.0 ile çalışmadığından, burada bunu yapmanın yeni bir yolunu anlatıyorum. Şimdi yapmanız gereken tek şey , aşağıda gösterildiği gibi OkHttpClient için özel durdurucularınızı eklemektir . FakeInterceptorsınıfı sadece interceptyöntemi geçersiz kılar ve uygulama DEBUGmodda ise JSON değerini döndürür.

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'daki projenin kaynak kodu


10
UnsupportedOperationException'ı önlemek için OkHttpClient.Builder'ı kullanın. son OkHttpClient okHttpClient = yeni OkHttpClient.Builder () .addInterceptor (yeni FakeInterceptor ()) .build ();
John

4
İki sorunum var: 1- uri()Altında yok chain.request().uri()( String url = chain.request().url().toString();durumum farklı olduğundan bunu düzelttim ). 2- alıyorum java.lang.IllegalStateException: network interceptor my.package.name.FakeInterceptor must call proceed() exactly once. Bunu addNetworkInterceptor()yerine ekledim addInterceptor().
Hesam

2
chain.request (). url (). uri () kullanın;
Amol Gupta

HttpClient.authenticator yöntemini test etmek için 401 hatasıyla nasıl taklit edebilirim? sadece kod "401" kimlik doğrulama yöntemi koyarak arama yapmayın. bunu nasıl halledebilirim?
Mehdi

Sahte önleme yaklaşımını bir sonraki seviyeye web API'leri alay etmek için aldım ve daha da kolay ve kullanışlı hale getirmek için küçük bir kitaplık yayınladım. Bkz. Github.com/donfuxx/Mockinizer
donfuxx

85

Yöntem 1'i aşağıdaki gibi denemeye karar verdim

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

Ve bunu kullanarak:

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

İyi çalışıyor ve gerçek sunucuyla iletişim kurmanıza gerek kalmadan json dizelerinizi test etmenizi sağlıyor!


Eskisi kullanımdan kaldırıldığı için kullanılan Response yapıcısını güncelledim, bu IllegalArgumentException url == nullda Retrofit 1.4.1 ile bir fırlatma yapıyordu .
Dan J

1
Ayrıca oluşturucuya bir uç nokta eklemeniz gerekir:builder.setEndpoint("http://mockserver.com").setClient(new MockClient());
kod önleme

URL isteğine bağlı olarak varlık klasöründeki bir dosyadan yanıtı almak için yukarıdaki sahte istemciyi genişlettim.
praveena_kd

21
Retrofit 2 artık istemci katmanı için OkHttpClient kullanıyor ve bu kod çalışmıyor. ¿OkHttpClient alayının nasıl yapılacağına dair bir fikriniz var mı? Muhtemelen her şey onu genişletmek ve geçersiz kılmakla ilgili, ancak nasıl olduğundan emin değilim.
GuillermoMP

1
Cevabınızı Retrofit2'ye göre de güncelleyebilir misiniz? teşekkürler
Hesam

20

JSON serisini kaldırmayı nesnelerinize test etmek (muhtemelen TypeAdapters? İle ), ayrı birim testleri gerektiren ayrı bir sorun gibi görünüyor.

Ben sürüm 2'yi kişisel olarak kullanıyorum. Kolayca hata ayıklanabilen ve değiştirilebilen tür açısından güvenli, yeniden yapılandırmaya uygun kod sağlar. Sonuçta, test için alternatif sürümlerini oluşturmuyorsanız, API'nizi arayüz olarak ilan etmenin ne faydası var! Kazanmak için çok biçimlilik.

Diğer bir seçenek de Java kullanmaktır Proxy. Aslında Retrofit (şu anda) temeldeki HTTP etkileşimini bu şekilde uygular. Bu kuşkusuz daha fazla çalışma gerektirecek, ancak çok daha dinamik taklitlere izin verecektir.


Bu benim de tercih ettiğim yol. Yukarıda belirtildiği gibi hata ayıklamak çok daha basittir ve ardından doğrudan yanıt gövdesi ile ilgilenmek zorunda kalırsınız. @alec GSON serileştirmesini test etmek istiyorsanız, bir json dizesi oluşturun / okuyun ve seriyi kaldırmak için bir gson nesnesi kullanın. Kafanın altında Retrofit'in yaptığı şeyin bu olduğuna inanıyorum.
loeschg

@JakeWharton Bunun neye benzediğine dair kısa bir örnek verebilir misiniz? Bunu görselleştirmede sorun yaşıyorum ... Teşekkürler!
uncle_tex



8

Gerçek bir sunucuya geçmeden önce bir API ile dalga geçmek için Apiary.io'nun büyük bir hayranıyım .

Ayrıca düz .json dosyalarını kullanabilir ve bunları dosya sisteminden okuyabilirsiniz.

Ayrıca Twitter, Flickr vb. Gibi herkesin erişebileceği API'leri de kullanabilirsiniz.

İşte Retrofit ile ilgili diğer bazı harika kaynaklar.

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

Örnek Proje: https://github.com/dustin-graham/ucad_twitter_retrofit_sample


7

Mockery (sorumluluk reddi: Ben yazarım) tam olarak bu görev için tasarlandı.

Mockery, Retrofit için yerleşik destekle ağ katmanlarını doğrulamaya odaklanan bir alay / test kitaplığıdır. JUnit testlerini belirli bir API'nin özelliklerine göre otomatik olarak oluşturur. Buradaki fikir herhangi bir testi manuel olarak yazmak zorunda kalmamaktır; sunucu yanıtlarını alay etmek için arabirimler uygulama.


7
  1. İlk önce Retrofit arayüzünüzü oluşturun.

    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. İstekte Bulunan kişi aşağıdaki gibidir:

    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. İkinci seçeneği kullanıyorsanız (Mock sunucu verilerine Retrofit arayüzünü kullanın), MockRetrofit'e ihtiyacınız var, aşağıdaki kodu kullanın:

    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. Verilerim varlık dosyasından (Asset / server / EventList.json), bu dosyanın içeriği:

    {
      "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 durdurucu kullanıyorsanız, aşağıdaki gibi kendi kendini tanımlayan engelleyiciye ihtiyacınız vardır:

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. Son olarak, sunucunuzdan kod talep edebilirsiniz:

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

Okuduğunuz için teşekkürler.


5

@Alec tarafından yanıta ekleyerek, istek URL'sine bağlı olarak yanıtı doğrudan varlık klasöründeki bir metin dosyasından almak için sahte istemciyi genişlettim.

Eski

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

Burada sahte istemci, tetiklenen URL'nin etkinleştirildiğini anlar ve varlıklar klasöründe activ.txt adlı bir dosya arar. İçeriği assets / activ.txt dosyasından okur ve API'ye yanıt olarak gönderir.

İşte genişletilmiş 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()));
    }
}

Ayrıntılı bir açıklama için bloguma bakabilirsiniz
http://www.cumulations.com/blogs/13/Mock-API-response-in-Retrofit-using-custom-clients


merhaba, robolectric kullanarak bir test sınıfı yazarken ve retrofit api ile alay etmek için sahte istemci kullandığımda, bu bana herhangi bir yanıt vermiyor. Bunun nasıl yapılacağına dair bana rehberlik edebilir misin?
Dory

Merhaba @Dory, varlıklar klasörünün içinde URL bitiş kısmına ve dosya adına sahip olduğunuzdan emin olun. Örneğin, URL'nizin aşağıdaki gibi olduğunu varsayalım (burada Reftrofit kullanarak) @POST ("/ redeemGyft") public void redeemGyft (@Body MposRequest reqdata, Callback <RedeemGyftResponse> geri araması); Sonra öğe klasöründe correspodning dosya adı redeemgyft.txt olduğunu
praveena_kd

MockClientDosyamda robolectric kullanarak bir test sınıfı yazdım , statik dosya adı verdim. Ancak json dosyasından herhangi bir yanıt alamıyorum.
Dory

Dosyayı varlıklar klasörünün içinde tuttuysanız, alması gerekir.
praveena_kd

1

JSONPlaceholder: Test ve Prototipleme için Fake Online REST API

https://jsonplaceholder.typicode.com/

ReqresIn: Başka Bir Çevrimiçi REST API'si

https://reqres.in/

Postacı sahte sunucusu

Özelleştirilmiş yanıt yükünü test etmek istiyorsanız, yukarıdaki ikisi gereksiniminize uymayabilir, ardından postacı sahte sunucuyu deneyebilirsiniz. Kendi isteğinizi ve yanıt yükünüzü tanımlamak oldukça kolaydır ve esnektir.

görüntü açıklamasını buraya girin https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/ https://youtu.be/shYn3Ys3ygE


1

Mockinizer ile API çağrılarını Retrofit ile alay etmek artık daha da kolay, bu da MockWebServer ile çalışmayı gerçekten basit hale getiriyor:

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

)

Bir RequestFilter ve MockResponses haritası oluşturun ve ardından bunu OkHttpClient oluşturucu zincirinize takın:

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

MockWebServer vb. Yapılandırma konusunda endişelenmenize gerek yok. Sadece taklitlerinizi ekleyin, gerisi sizin için Mockinizer tarafından yapılır.

(Feragatname: Mockinizer'ın yazarıyım)


0

Benim için özel Retrofit Client esneklik nedeniyle harika. Özellikle herhangi bir DI çerçevesini kullandığınızda, hızlı ve basit bir şekilde aç / kapa taklidini açabilirsiniz. Dagger tarafından sağlanan özel İstemci birim ve entegrasyon testlerinde de kullanıyorum.

Düzenleme: Burada, alaycı güçlendirme örneğini bulabilirsiniz https://github.com/pawelByszewski/retrofitmock

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.