目前的Android单元测试,不少都基于Roboletric框架,回避了Instrumentation test必须启动虚拟机或者真机的麻烦,执行效率大大提升。这里不讨论测试框架的选择问题,网络上有不少关于此类的资料。同时,如今几乎全部的App都会进行网络数据通讯,Retrofit2就是其中很是方便的一个网络框架,遵循Restful接口设计。如此,再进行Android单元测试时,就必然须要绕过Retrofit的真实网络请求,mock出不一样的response来进行本地逻辑测试。java
retrofit官方出过单元测试的方法和介绍,详见参考文献4,介绍的很是细致。可是该方法是基于Instrumentation的,若是基于Robolectric框架,对于异步的请求就会出现问题,在stackoverflow上面有关于异步问题的描述,也给出了一个解决方法,可是须要对源码进行改动,因此不完美。本文将针对Robolectric+Retrofit2的单元测试过程当中异步问题如何解决,提出一种更完美的解决方法。有理解不当的,后者更好的方案,欢迎你们提出指正。git
通常使用retrofit2的时候,会出现一下代码片断github
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 } }); }
单元测试会测试testMethod方法,触发后根据不一样的response,校验对应的逻辑处理,如上面的“// Deal with the successful case” 和 “// Deal with the failure case”。为了达到这个目的,须要实现一下两点:1)当触发该方法时,不会走真实的网络;2)能够mock不一样的response进行测试json
第一点能够借助MockWebServer来实现,具体的实现方法能够参考文献4,这里不展开了,重点看下第二点。在文献4中的sample#1,经过一个json文件,清晰简单的代表了测试的目的,因此咱们也但愿用这种方式。可是当实现后测试却发现,上面赋值给call.enqueue的Callback,不管是onResponse仍是onFailure都不会被调用。后来在stackoverflow上面发现了文献3,再结合本身的测试,发现根本的缘由在于call.enqueue是异步的。当单元测试已经结束时,enqueue的异步处理尚未结束,因此Callback根本没有被调用。那么网络是否执行了呢?经过打开OkhttpClient的log能够看到,MockWebServer的request和response都出现了,说明网络请求已经模拟执行了。产生这个问题跟Robolectric框架的实现有必定的关系,更进一步的具体缘由,有兴趣你们能够进一步研究,也许会发现新的思路。网络
知道是因为异步致使的,那解决的思路就简单了,经过mock手段,将异步执行变成同步执行。那么如何mock呢,咱们能够经过retrofit的源码来查看。app
经过Retrofit的create方法能够获取service,先来看看create这个方法的实现框架
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); } }); }
从代码能够看出,经过service.getxxx()来得到Call<xxxService>的时候,实际得到的是OkHttpCall。那么call.enqueue实际调用的也是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(); } } }); }
这里经过createRawCall方法来得到真正执行equeue的类,再看看这个方法的实现:ide
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来自于serviceMethod.callFactory.newCall(request),那么serviceMethod.callFactory又是从哪里来的呢。打开ServiceMethod<T>这个类,在构造函数中有以下代码:函数
this.callFactory = builder.retrofit.callFactory();
说明这个callFactory来自于retrofit.callFactory(),进一步查看Retrofit类的源码:
okhttp3.Call.Factory callFactory = this.callFactory; if (callFactory == null) { callFactory = new OkHttpClient(); }
在经过Retrofit.Builder建立retrofit实例的时候,能够经过下面的方法设置factory实例,若是不设置,默认会建立一个OkHttpClient。
public Builder callFactory(okhttp3.Call.Factory factory) { this.callFactory = checkNotNull(factory, "factory == null"); return this; }
到这里全部的脉络都清楚了,若是建立Retrofit实例时,设置咱们本身的callFactory,在该factory中,调用的call.enqueue将根据设置的response直接调用callback中的onResponse或者onFailure方法,从而回避掉异步的问题。具体的实现代码以下:
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; } } }
下面看下单元测试的时候怎么用。
1)经过反射或者mock,修改被测代码中的retrofit实例,调用callFactory来设置上面的MockFactory
2)准备好要返回的response,设置MockFactory的mockResponse,调用被测方法,校验结果
@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 }
参考文献:
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/