目前的Android單元測(cè)試,很多都基于Roboletric框架,回避了Instrumentation test必須啟動(dòng)虛擬機(jī)或者真機(jī)的麻煩,執(zhí)行效率大大提高。這里不討論測(cè)試框架的選擇問(wèn)題,網(wǎng)絡(luò)上有很多關(guān)于此類的資料。同時(shí),現(xiàn)在幾乎所有的App都會(huì)進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)通信,Retrofit2就是其中非常方便的一個(gè)網(wǎng)絡(luò)框架,遵循Restful接口設(shè)計(jì)。如此,再進(jìn)行Android單元測(cè)試時(shí),就必然需要繞過(guò)Retrofit的真實(shí)網(wǎng)絡(luò)請(qǐng)求,mock出不同的response來(lái)進(jìn)行本地邏輯測(cè)試。
成都網(wǎng)站建設(shè)、網(wǎng)站建設(shè)的開(kāi)發(fā),更需要了解用戶,從用戶角度來(lái)建設(shè)網(wǎng)站,獲得較好的用戶體驗(yàn)。創(chuàng)新互聯(lián)公司多年互聯(lián)網(wǎng)經(jīng)驗(yàn),見(jiàn)的多,溝通容易、能幫助客戶提出的運(yùn)營(yíng)建議。作為成都一家網(wǎng)絡(luò)公司,打造的就是網(wǎng)站建設(shè)產(chǎn)品直銷的概念。選擇創(chuàng)新互聯(lián)公司,不只是建站,我們把建站作為產(chǎn)品,不斷的更新、完善,讓每位來(lái)訪用戶感受到浩方產(chǎn)品的價(jià)值服務(wù)。
retrofit官方出過(guò)單元測(cè)試的方法和介紹,詳見(jiàn)參考文獻(xiàn)4,介紹的非常細(xì)致。但是該方法是基于Instrumentation的,如果基于Robolectric框架,對(duì)于異步的請(qǐng)求就會(huì)出現(xiàn)問(wèn)題,在stackoverflow上面有關(guān)于異步問(wèn)題的描述,也給出了一個(gè)解決方法,但是需要對(duì)源碼進(jìn)行改動(dòng),所以不完美。本文將針對(duì)Robolectric+Retrofit2的單元測(cè)試過(guò)程中異步問(wèn)題如何解決,提出一種更完美的解決方法。有理解不當(dāng)?shù)?,后者更好的方案,歡迎大家提出指正。
一般使用retrofit2的時(shí)候,會(huì)出現(xiàn)一下代碼片段
public void testMethod() { OkHttpClient client = new OkHttpClient(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(JacksonConverterFactory.create()) .client(client) .build(); service = retrofit.create(xxxService.class); Call<xxxService> call = service.getxxx(); call.enqueue(new Callback<xxx>() { @Override public void onResponse(Call<xxx> call, Response<xxxResponse> response) { // Deal with the successful case } @Override public void onFailure(Call<xxxResponse> call, Throwable t) { // Deal with the failure case } }); }
單元測(cè)試會(huì)測(cè)試testMethod方法,觸發(fā)后根據(jù)不同的response,校驗(yàn)對(duì)應(yīng)的邏輯處理,如上面的“// Deal with the successful case” 和 “// Deal with the failure case”。為了達(dá)到這個(gè)目的,需要實(shí)現(xiàn)一下兩點(diǎn):1)當(dāng)觸發(fā)該方法時(shí),不會(huì)走真實(shí)的網(wǎng)絡(luò);2)可以mock不同的response進(jìn)行測(cè)試
第一點(diǎn)可以借助MockWebServer來(lái)實(shí)現(xiàn),具體的實(shí)現(xiàn)方法可以參考文獻(xiàn)4,這里不展開(kāi)了,重點(diǎn)看下第二點(diǎn)。在文獻(xiàn)4中的sample#1,通過(guò)一個(gè)json文件,清晰簡(jiǎn)單的表明了測(cè)試的目的,所以我們也希望用這種方式。但是當(dāng)實(shí)現(xiàn)后測(cè)試卻發(fā)現(xiàn),上面賦值給call.enqueue的Callback,無(wú)論是onResponse還是onFailure都不會(huì)被調(diào)用。后來(lái)在stackoverflow上面發(fā)現(xiàn)了文獻(xiàn)3,再結(jié)合自己的測(cè)試,發(fā)現(xiàn)根本的原因在于call.enqueue是異步的。當(dāng)單元測(cè)試已經(jīng)結(jié)束時(shí),enqueue的異步處理還沒(méi)有結(jié)束,所以Callback根本沒(méi)有被調(diào)用。那么網(wǎng)絡(luò)是否執(zhí)行了呢?通過(guò)打開(kāi)OkhttpClient的log可以看到,MockWebServer的request和response都出現(xiàn)了,說(shuō)明網(wǎng)絡(luò)請(qǐng)求已經(jīng)模擬執(zhí)行了。產(chǎn)生這個(gè)問(wèn)題跟Robolectric框架的實(shí)現(xiàn)有一定的關(guān)系,更進(jìn)一步的具體原因,有興趣大家可以進(jìn)一步研究,也許會(huì)發(fā)現(xiàn)新的思路。
知道是由于異步導(dǎo)致的,那解決的思路就簡(jiǎn)單了,通過(guò)mock手段,將異步執(zhí)行變成同步執(zhí)行。那么如何mock呢,我們可以通過(guò)retrofit的源碼來(lái)查看。
通過(guò)Retrofit的create方法可以獲取service,先來(lái)看看create這個(gè)方法的實(shí)現(xiàn)
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }
從代碼可以看出,通過(guò)service.getxxx()來(lái)獲得Call<xxxService>的時(shí)候,實(shí)際獲得的是OkHttpCall。那么call.enqueue實(shí)際調(diào)用的也是OkHttpCall的enqueue方法,其源碼如下:
@Override public void enqueue(final Callback<T> callback) { if (callback == null) throw new NullPointerException("callback == null"); okhttp3.Call call; Throwable failure; synchronized (this) { if (executed) throw new IllegalStateException("Already executed."); executed = true; call = rawCall; failure = creationFailure; if (call == null && failure == null) { try { call = rawCall = createRawCall(); } catch (Throwable t) { failure = creationFailure = t; } } } if (failure != null) { callback.onFailure(this, failure); return; } if (canceled) { call.cancel(); } call.enqueue(new okhttp3.Callback() { @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) throws IOException { Response<T> response; try { response = parseResponse(rawResponse); } catch (Throwable e) { callFailure(e); return; } callSuccess(response); } @Override public void onFailure(okhttp3.Call call, IOException e) { try { callback.onFailure(OkHttpCall.this, e); } catch (Throwable t) { t.printStackTrace(); } } private void callFailure(Throwable e) { try { callback.onFailure(OkHttpCall.this, e); } catch (Throwable t) { t.printStackTrace(); } } private void callSuccess(Response<T> response) { try { callback.onResponse(OkHttpCall.this, response); } catch (Throwable t) { t.printStackTrace(); } } }); }
這里通過(guò)createRawCall方法來(lái)獲得真正執(zhí)行equeue的類,再看看這個(gè)方法的實(shí)現(xiàn):
private okhttp3.Call createRawCall() throws IOException { Request request = serviceMethod.toRequest(args); okhttp3.Call call = serviceMethod.callFactory.newCall(request); if (call == null) { throw new NullPointerException("Call.Factory returned null."); } return call; }
真正的okhttp3.Call來(lái)自于serviceMethod.callFactory.newCall(request),那么serviceMethod.callFactory又是從哪里來(lái)的呢。打開(kāi)ServiceMethod<T>這個(gè)類,在構(gòu)造函數(shù)中有如下代碼:
this.callFactory = builder.retrofit.callFactory();
說(shuō)明這個(gè)callFactory來(lái)自于retrofit.callFactory(),進(jìn)一步查看Retrofit類的源碼:
okhttp3.Call.Factory callFactory = this.callFactory; if (callFactory == null) { callFactory = new OkHttpClient(); }
在通過(guò)Retrofit.Builder創(chuàng)建retrofit實(shí)例的時(shí)候,可以通過(guò)下面的方法設(shè)置factory實(shí)例,如果不設(shè)置,默認(rèn)會(huì)創(chuàng)建一個(gè)OkHttpClient。
public Builder callFactory(okhttp3.Call.Factory factory) { this.callFactory = checkNotNull(factory, "factory == null"); return this; }
到這里所有的脈絡(luò)都清楚了,如果創(chuàng)建Retrofit實(shí)例時(shí),設(shè)置我們自己的callFactory,在該factory中,調(diào)用的call.enqueue將根據(jù)設(shè)置的response直接調(diào)用callback中的onResponse或者onFailure方法,從而回避掉異步的問(wèn)題。具體的實(shí)現(xiàn)代碼如下:
public class MockFactory extends OkHttpClient { private MockCall mockCall; public MockFactory() { mockCall = new MockCall(); } public void mockResponse(Response.Builder mockBuilder) { mockCall.setResponseBuilder(mockBuilder); } @Override public Call newCall(Request request) { mockCall.setRequest(request); return mockCall; } public class MockCall implements Call { // Guarded by this. private boolean executed; volatile boolean canceled; /** The application's original request unadulterated by redirects or auth headers. */ Request originalRequest; Response.Builder mockResponseBuilder; HttpEngine engine; protected MockCall() {} // protected MockCall(Request originalRequest, boolean mockFailure, // Response.Builder mockResponseBuilder) { // this.originalRequest = originalRequest; // this.mockFailure = mockFailure; // this.mockResponseBuilder = mockResponseBuilder; // this.mockResponseBuilder.request(originalRequest); // } public void setRequest(Request originalRequest) { this.originalRequest = originalRequest; } public void setResponseBuilder(Response.Builder mockResponseBuilder) { this.mockResponseBuilder = mockResponseBuilder; } @Override public Request request() { return originalRequest; } @Override public Response execute() throws IOException { return mockResponseBuilder.request(originalRequest).build(); } @Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } int code = mockResponseBuilder.request(originalRequest).build().code(); if (code >= 200 && code < 300) { try { if (mockResponseBuilder != null) { responseCallback.onResponse(this, mockResponseBuilder.build()); } } catch (IOException e) { // Nothing } } else { responseCallback.onFailure(this, new IOException("Mock responseCallback onFailure")); } } @Override public void cancel() { canceled = true; if (engine != null) engine.cancel(); } @Override public synchronized boolean isExecuted() { return executed; } @Override public boolean isCanceled() { return canceled; } } }
下面看下單元測(cè)試的時(shí)候怎么用。
1)通過(guò)反射或者mock,修改被測(cè)代碼中的retrofit實(shí)例,調(diào)用callFactory來(lái)設(shè)置上面的MockFactory
2)準(zhǔn)備好要返回的response,設(shè)置MockFactory的mockResponse,調(diào)用被測(cè)方法,校驗(yàn)結(jié)果
@Test public void testxxx() throws Exception { ResponseBody responseBody = ResponseBody.create(MediaType.parse("application/json"), RestServiceTestHelper.getStringFromFile("xxx.json")); Response.Builder mockBuilder = new Response.Builder() .addHeader("Content-Type", "application/json") .protocol(Protocol.HTTP_1_1) .code(200) .body(responseBody); mMockFactory.mockResponse(mockBuilder); // call the method to be tested // verfify if the result is expected }
參考文獻(xiàn):
1. robolectric.org
2. https://square.github.io/retrofit/
3. http://stackoverflow.com/questions/37909276/testing-retrofit-2-with-robolectric-callbacks-not-being-called
4. https://riggaroo.co.za/retrofit-2-mocking-http-responses/
新聞標(biāo)題:Roboletric+Retrofit2單元測(cè)試
文章來(lái)源:http://vcdvsql.cn/article32/podopc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供ChatGPT、網(wǎng)站排名、定制開(kāi)發(fā)、外貿(mào)網(wǎng)站建設(shè)、品牌網(wǎng)站建設(shè)、營(yíng)銷型網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)