OkHttp3系列(二)MockWebServer使用
OkHttp3
是由Square
貢獻的HTTP
客戶端框架,主要用在Andorid
中,但是由於其易用的API、強大的功能、請求的快速等特點,也被大量採用在後端開發領域。本系列文章講述OkHttp3
的基本使用、OkHttp3
的高階功能以及OkHttp3
原始碼的解析等,請持續關注。
本片文章是此係列的第二篇。
Mock
mock
在測試領域是很重要的一個概念。mock
測試就是在測試過程中,對於某些不容易構造或者不容易獲取的物件,建立用一個虛擬的物件以方便測試的測試方法。比如在Java中可以藉助JMock、EasyMock等工具建立Java物件,幫助我們快速進行單元測試。
MockWebServer
OkHttp3
提供的一個快速建立HTTP服務端的工具。當我們的服務需要依賴外部HTTP應用時,可以按照預期功能快速構建外部HTTP應用,加快開發流程,快速進行單元測試,完善程式碼。搭配OkHttp3
使用時,可以測試我們自己編寫的OkHttp3
客戶端程式碼。
目前Java版本的MockWebServer
最後版本的Maven座標如下,本編文章的程式碼示例均基於該版本。
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>mockwebserver</artifactId> <version>3.14.9</version> </dependency>
使用MockWebServer
基本示例
MockWebServer
的使用很簡單。
首先建立一個MockWebServer
物件。
MockWebServer server = new MockWebServer();
然後建立響應內容。
MockResponse mockResponse = new MockResponse().setBody("hello, world!")
把響應內容放入MockWebServer
物件。
server.enqueue(mockResponse);
啟動MockWebServer
。
try { server.start(8080); } catch (IOException e) { e.printStackTrace(); }
高階功能
模擬弱網環境響應。
MockWebServer server = new MockWebServer();
String filePath = "C:\\Users\\weegee\\Downloads\\dm-algo-top10.pdf";
Buffer bodyBuffer = new Buffer();
bodyBuffer.readFrom(new FileInputStream(new File(filePath)));
MockResponse bigMockResponse = new MockResponse()
.addHeader("Cache-Control", "no-cache")
.setBody(bodyBuffer);
bigMockResponse.setBodyDelay(5, TimeUnit.SECONDS);
bigMockResponse.throttleBody(1024 * 1024, 1, TimeUnit.SECONDS);
server.enqueue(bigMockResponse);
try {
server.start(8099);
} catch (IOException e) {
e.printStackTrace();
System.exit(0);
}
throttleBody(long, long, TimeUnit):MockResponse
方法的第一個引數是傳輸位元組數,第二個引數是傳輸第一個引數指定的位元組數需要的時間,第三個引數是時間單位。
上述示例模擬每秒僅能傳輸1MB位元組資料的網路狀況,響應體大小為4.2MB,同時設定響應延遲5秒才可開始處理。則該請求正常情況下應該需要9秒多才可以結束,我們用Postman作為請求客戶端模擬這一情況。
服務端請求分發
正常的HTTP請求一般會對不同路徑的請求返回不同的結果,MockWebServer
通過Dispatcher
支援該功能。
MockWebServer server = new MockWebServer();
final Dispatcher dispatcher = new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
if (request.getPath().equals("/v1/login/auth/")){
return new MockResponse().setResponseCode(200);
} else if (request.getPath().equals("/v1/check/version")){
return new MockResponse().setResponseCode(200).setBody("version=9");
} else if (request.getPath().equals("/v1/profile/info")) {
return new MockResponse().setResponseCode(200).setBody("{\\\"info\\\":{\\\"name\":\"Lucas Albuquerque\",\"age\":\"21\",\"gender\":\"male\"}}");
}
return new MockResponse().setResponseCode(404);
}
};
server.setDispatcher(dispatcher);
try {
server.start(8099);
} catch (IOException e) {
e.printStackTrace();
System.exit(0);
}
上述示例設定了三個請求路徑/v1/login/auth/
、/v1/check/version
、/v1/profile/info
,客戶端對對應路徑的請求返回對應的結果。
注意: setDispatcher
和enqueue
不能同時使用,若是先使用enqueue
再使用setDispatcher
,則enqueue
的響應體則會丟失,而先setDispatcher
再enqueue
則執行失敗,丟擲異常java.lang.ClassCastException
。原因在於MockWebServer
對於所有的響應體MockResponse
都是通過一個分發器處理,下面擷取MockWebServer
的部分原始碼進行分析。
// 唯一分發器
private Dispatcher dispatcher = new QueueDispatcher();
// enqueue方法
public void enqueue(MockResponse response) {
((QueueDispatcher)this.dispatcher).enqueueResponse(response.clone());
}
// setDispatcher方法,呼叫該方法會把之前dispatcher內容拋棄,使用方法引數中的dispatcher覆蓋原來的內容
public void setDispatcher(Dispatcher dispatcher) {
if (dispatcher == null) {
throw new NullPointerException();
} else {
this.dispatcher = dispatcher;
}
}
先enqueue
再setDispatcher
比較好理解,但是先setDispatcher
再enqueue
時,發生了以下異常。
Exception in thread "main" java.lang.ClassCastException: class com.github.chengtengfei.http.OkHttp3MockWebServer$1 cannot be cast to class okhttp3.mockwebserver.QueueDispatcher
異常發生的位置跟蹤到是。
((QueueDispatcher)this.dispatcher).enqueueResponse(response.clone());
也就是說,呼叫setDispatcher
方法後,this.dispatcher
就不再是QueueDispatcher
型別的了,而變成了com.github.chengtengfei.http.OkHttp3MockWebServer$1
這個型別。
一般類名加$1
表明當前類中存在內部匿名類,所以編譯後以數字代替類名稱,反編譯OkHttp3MockWebServer$1
可獲得以下內容。
class OkHttp3MockWebServer$1 extends Dispatcher {
OkHttp3MockWebServer$1() {
}
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
if (request.getPath().equals("/v1/login/auth/")) {
return (new MockResponse()).setResponseCode(200);
} else if (request.getPath().equals("/v1/check/version")) {
return (new MockResponse()).setResponseCode(200).setBody("version=9");
} else {
return request.getPath().equals("/v1/profile/info") ? (new MockResponse()).setResponseCode(200).setBody("{\\\"info\\\":{\\\"name\":\"Lucas Albuquerque\",\"age\":\"21\",\"gender\":\"male\"}}") : (new MockResponse()).setResponseCode(404);
}
}
}
呼叫setDispatcher
前,建立了dispatcher
物件,該物件是一個內部類的物件,該內部類繼承自okhttp3.mockwebserver.Dispatcher
然後實現了dispatch(RecordedRequest):MockResponse
方法,方法體中實現了自定義的分發機制,呼叫setDispatcher
傳入的引數型別是內部類的型別,和原來的QueueDispatcher
不是同一型別,所以進行強制型別轉換就會丟擲異常。
請求記錄
MockWebServer
支援對客戶端的每次請求進行記錄,藉助該功能可以觀察請求時攜帶的引數、請求頭等內容。
這裡我們以前一小節的請求分發為例構建服務端,然後列印每次請求的路徑。
MockWebServer server = new MockWebServer();
final Dispatcher dispatcher = new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
if (request.getPath().equals("/v1/login/auth")){
return new MockResponse().setResponseCode(200);
} else if (request.getPath().equals("/v1/check/version")){
return new MockResponse().setResponseCode(200).setBody("version=9");
} else if (request.getPath().equals("/v1/profile/info")) {
return new MockResponse().setResponseCode(200).setBody("{\\\"info\\\":{\\\"name\":\"Lucas Albuquerque\",\"age\":\"21\",\"gender\":\"male\"}}");
}
return new MockResponse().setResponseCode(404);
}
};
server.setDispatcher(dispatcher);
try {
server.start(8099);
} catch (IOException e) {
e.printStackTrace();
System.exit(0);
}
while (true) {
RecordedRequest request = server.takeRequest();
System.out.println(request.getPath());
}
小結
本片文章介紹了OkHttp3
提供的MockWebServer
功能,並詳細介紹了它的使用。下篇文章將會介紹一款由筆者打造的用來快速使用OkHttp3
的框架,敬請期待。