Android單元測試(五):網路介面測試
溫馨提示:如果你不太熟悉單元測試,可以先看下之前四篇基礎框架使用。便於你更好的理解下面的內容。
在平日的開發中,我們用後臺寫好給我們介面去獲取資料。雖然我們有一些請求介面的工具,可以快速的拿到返回資料。但是在一些異常情況的處理上就不太方便了。我列出以下幾個痛點:
快速的檢視返回資料與資料的處理。(一般我們都是將寫好的程式碼跑到手機上,點選到對應頁面的對應按鈕)
異常資訊的返回與處理。比如一個介面返回一個列表資料,如果列表為空呢?(找後臺給我們模擬資料)網速不好呢?(不知道怎麼搞…)網路異常呢?(關閉網路)
後臺有部分介面沒有寫好,你就只能等他了。
不知道上面的這三點有沒有戳到你的痛處。如果扎心了,那麼老鐵你就有必要掌握今天的內容。
1.請求介面
我們就使用網路請求三件套(retrofit + okhttp + rxjava2)來舉例。
首先新增一下依賴,同時記得加網路許可權。
//RxJava
compile 'io.reactivex.rxjava2:rxjava:2.1.7'
//RxAndroid
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
//okhttp
compile "com.squareup.okhttp3:okhttp:3.9.1"
//Retrofit
compile ("com.squareup.retrofit2:retrofit:2.3.0" ){
exclude module: 'okhttp'
}
compile ("com.squareup.retrofit2:adapter-rxjava2:2.3.0"){
exclude module: 'rxjava'
}
compile "com.squareup.retrofit2:converter-gson:2.3.0"
測試介面:
public interface GithubApi {
String BASE_URL = "https://api.github.com/";
@GET("users/{username}" )
Observable<User> getUser(@Path("username") String username);
}
Retrofit
的初始化,我們使用LoggingInterceptor
來列印返回資料。
public class GithubService {
private static Retrofit retrofit = new Retrofit.Builder()
.baseUrl(GithubApi.BASE_URL)
.client(getOkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
public static GithubApi createGithubService() {
return retrofit.create(GithubApi.class);
}
private static OkHttpClient getOkHttpClient(){
return new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
}
}
測試程式碼:
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class ResponseTest {
@Before
public void setUp() {
ShadowLog.stream = System.out;
initRxJava2();
}
private void initRxJava2() {
RxJavaPlugins.reset();
RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return Schedulers.trampoline();
}
});
RxAndroidPlugins.reset();
RxAndroidPlugins.setMainThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return Schedulers.trampoline();
}
});
}
@Test
public void getUserTest() {
GithubService.createGithubService()
.getUser("simplezhli")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onSubscribe(Disposable d) {}
@Override
public void onNext(User user) {
assertEquals("唯鹿", user.name);
assertEquals("http://blog.csdn.net/qq_17766199", user.blog);
}
@Override
public void onError(Throwable e) {
Log.e("Test", e.toString());
}
@Override
public void onComplete() {}
});
}
}
上面的程式碼中,因為網路請求是非同步的,所以我們直接測試是不能直接拿到資料,因此無法打印出Log以及測試。所以我們使用initRxJava2()
方法將非同步轉化為同步。這樣我們就可以看到返回資訊。測試結果如下:
上面的例子為了簡單直觀的說明,所以將請求介面的方法寫到了測試類中,實際中我們可以Mock方法所在的類直接呼叫請求方法。配合Robolectric
對View控制元件的狀態進行測試。
如果你覺得每次測試都要加initRxJava2
這段方法很麻煩,你可以抽象出來,或者使用@Rule
。
public class RxJavaRule implements TestRule {
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
RxJavaPlugins.reset();
RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return Schedulers.trampoline();
}
});
RxAndroidPlugins.reset();
RxAndroidPlugins.setMainThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return Schedulers.trampoline();
}
});
base.evaluate();
}
};
}
}
2.模擬資料
1.使用攔截器模擬資料
利用okhttp的攔截器模擬響應資料。
public class MockInterceptor implements Interceptor {
private final String responseString; //你要模擬返回的資料
public MockInterceptor(String responseString) {
this.responseString = responseString;
}
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Response 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();
return response;
}
}
測試程式碼:
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class MockGithubServiceTest {
private GithubApi mockGithubService;
@Rule
public RxJavaRule rule = new RxJavaRule();
@Before
public void setUp() throws URISyntaxException {
ShadowLog.stream = System.out;
//定義Http Client,並新增攔截器
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.addInterceptor(new MockInterceptor("json資料"))//<-- 新增攔截器
.build();
//設定Http Client
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(GithubApi.BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
mockGithubService = retrofit.create(GithubApi.class);
}
@Test
public void getUserTest() throws Exception {
mockGithubService.getUser("weilu") //<-- 這裡傳入錯誤的使用者名稱
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onSubscribe(Disposable d) {}
@Override
public void onNext(User user) {
assertEquals("唯鹿", user.name);
assertEquals("http://blog.csdn.net/qq_17766199", user.blog);
}
@Override
public void onError(Throwable e) {
Log.e("Test", e.toString());
}
@Override
public void onComplete() {}
});
}
}
雖然我們傳入了錯誤的使用者名稱,但是我們模擬的響應資訊已經提前設定好了,所以測試結果不變。
利用這個思路,我們可以修改MockInterceptor
的code,模擬404的情況。
2.MockWebServer
MockWebServer是square出品的跟隨okhttp一起釋出,用來Mock伺服器行為的庫。MockWebServer能幫我們做的事情:
- 可以設定http response的header、body、status code等。
- 可以記錄接收到的請求,獲取請求的body、header、method、path、HTTP version。
- 可以模擬網速慢的網路環境。
- 提供Dispatcher,讓mockWebServer可以根據不同的請求進行不同的反饋。
新增依賴:
testCompile 'com.squareup.okhttp3:mockwebserver:3.9.1'
測試程式碼:
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class MockWebServerTest {
private GithubApi mockGithubService;
private MockWebServer server;
@Rule
public RxJavaRule rule = new RxJavaRule();
@Before
public void setUp(){
ShadowLog.stream = System.out;
// 建立一個 MockWebServer
server = new MockWebServer();
//設定響應,預設返回http code是 200
MockResponse mockResponse = new MockResponse()
.addHeader("Content-Type", "application/json;charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody("{\"id\": 12456431, " +
" \"name\": \"唯鹿\"," +
" \"blog\": \"http://blog.csdn.net/qq_17766199\"}");
MockResponse mockResponse1 = new MockResponse()
.addHeader("Content-Type", "application/json;charset=utf-8")
.setResponseCode(404)
.throttleBody(5, 1, TimeUnit.SECONDS) //一秒傳遞5個位元組,模擬網速慢的情況
.setBody("{\"error\": \"網路異常\"}");
server.enqueue(mockResponse); //成功響應
server.enqueue(mockResponse1);//失敗響應
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://" + server.getHostName() + ":" + server.getPort() + "/") //設定對應的Host與埠號
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
mockGithubService = retrofit.create(GithubApi.class);
}
@Test
public void getUserTest() throws Exception {
//請求不變
mockGithubService.getUser("simplezhli")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(User user) {
assertEquals("唯鹿", user.name);
assertEquals("http://blog.csdn.net/qq_17766199", user.blog);
}
@Override
public void onError(Throwable e) {
Log.e("Test", e.toString());
}
@Override
public void onComplete() {
}
});
//驗證我們的請求客戶端是否按預期生成了請求
RecordedRequest request = server.takeRequest();
assertEquals("GET /users/simplezhli HTTP/1.1", request.getRequestLine());
assertEquals("okhttp/3.9.1", request.getHeader("User-Agent"));
// 關閉服務
server.shutdown();
}
}
程式碼中的註釋寫的很清楚了,就不用過多的解釋了,使用起來非常的簡單。
執行結果(成功):
失敗結果:
預設情況下 MockWebServer
預置的響應是先進先出的。這樣可能對你的測試有限制,這時可以通過Dispatcher
來處理,比如通過請求的路徑來選擇轉發。
Dispatcher dispatcher = new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
if (request.getPath().equals("/users/simplezhli")){
return new MockResponse()
.addHeader("Content-Type", "application/json;charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody("{\"id\": 12456431, " +
" \"name\": \"唯鹿\"," +
" \"blog\": \"http://blog.csdn.net/qq_17766199\"}");
} else {
return new MockResponse()
.addHeader("Content-Type", "application/json;charset=utf-8")
.setResponseCode(404)
.throttleBody(5, 1, TimeUnit.SECONDS) //一秒傳遞5個位元組
.setBody("{\"error\": \"網路異常\"}");
}
}
};
server.setDispatcher(dispatcher); //設定Dispatcher
通過上面的例子,是不是可以很好的解決你的痛點,希望對你有幫助。只要我們有和後臺有開發文件,約定好資料格式與欄位名,那麼我們可以更敏捷的去做我們的開發。本篇所有程式碼已上傳至Github。希望大家多多點贊支援!