OkHttp3系列(二)MockWebServer使用
OkHttp3
是由Square
贡献的HTTP
客户端框架,主要用在Andorid
中,但是由于其易用的API、强大的功能、请求的快速等特点,也被大量采用在后端开发领域。本系列文章讲述OkHttp3
的基本使用、OkHttp3
的高级功能以及OkHttp3
源码的解析等,请持续关注。
本篇文章是此系列的第二篇。
Mock#
mock
在测试领域是很重要的一个概念。mock
测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,创建用一个虚拟的对象以方便测试的测试方法。比如在Java中可以借助JMock、EasyMock等工具创建Java对象,帮助我们快速进行单元测试。
MockWebServer#
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
的框架,敬请期待。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验