概述
我搜索了MockWebServer,该库对于测试HTTP客户端非常有用。
什么是MockWebServer?
我认为我创建了一个用于测试OkHttp的库,并且在OkHttp存储库中有一个项目。该许可证是Apache许可证,版本2.0。
它可以用来模拟HTTP通信部分。如今,不使用HTTP通信的应用程序已不太可能,因此感觉需求非常高。
什么是OkHttp?
由Square,Inc.开发的HTTP客户端,它为Android提供了大量有用的库。由于它是一个普通的Java库,因此也可以在Web应用程序和业务应用程序中使用
在Android区域中,Apache的HTTP客户端已弃用→已从Android OS 5.x中删除,它作为替代库吸引了人们的注意。
什么是Square,Inc.?
是一家美国支付服务公司,在日本,它是为Android提供便捷库的公司。首席执行官是Twitter的联合创始人Jack Dorsey。
趋势
Java测试工具趋势容易理解,Google趋势图是在2014/1至2016/5附加的,因此我将模仿它。两者均为2012年1月至2016年6月的图表。
MockWebServer
好的Http
开始使用
build.gradle
您可以通过添加一个
依赖项来使用它。请注意,OkHttp包含在依赖项中。
build.gradle
1 2 3 4 | dependencies { ...... testCompile 'com.squareup.okhttp3:mockwebserver:3.2.0' } |
(可选)Eclipse集合
添加了Eclipse Collections以使用以下类。它不是必需的,但很有用。
build.gradle
1 2 3 4 | dependencies { ...... compile 'org.eclipse.collections:eclipse-collections:7.1.0' } |
玩官方示例代码
当我阅读用GitHub存储库的README.md编写的示例代码时,我根本不理解其含义,因此我重写了它,以便自己理解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | import java.io.IOException; import org.eclipse.collections.impl.block.factory.Procedures; import org.eclipse.collections.impl.list.Interval; import org.junit.Test; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; public class MockServerTest { @Test public void test() throws IOException, InterruptedException { // Create a MockWebServer. These are lean enough that you can create a new // instance for every unit test. final MockWebServer server = new MockWebServer(); // Schedule some responses. server.enqueue(new MockResponse().setBody("hello, world!")); server.enqueue(new MockResponse().setBody("sup, bra?")); server.enqueue(new MockResponse().setBody("yo dog")); // Start the server. server.start(); // Ask the server for its URL. You'll need this to make HTTP requests. final OkHttpClient client = new OkHttpClient(); Interval.oneTo(3).each(Procedures.throwing(i -> { final Request request = new Request.Builder().url(server.url("/v1/chat/")).build(); final Response response = client.newCall(request).execute(); System.out.println(i); System.out.println(response.headers()); System.out.println(response.body().string()); })); // Shut down the server. Instances cannot be reused. server.shutdown(); } } |
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 6 11, 2016 6:44:23 午後 okhttp3.mockwebserver.MockWebServer$3 execute 情報: MockWebServer[64145] starting to accept connections 6 11, 2016 6:44:23 午後 okhttp3.mockwebserver.MockWebServer$4 processOneRequest 情報: MockWebServer[64145] received request: GET /v1/chat/ HTTP/1.1 and responded: HTTP/1.1 200 OK 1 Content-Length: 13 OkHttp-Sent-Millis: 1465638263689 OkHttp-Received-Millis: 1465638263695 hello, world! 6 11, 2016 6:44:23 午後 okhttp3.mockwebserver.MockWebServer$4 processOneRequest 情報: MockWebServer[64145] received request: GET /v1/chat/ HTTP/1.1 and responded: HTTP/1.1 200 OK 2 Content-Length: 9 OkHttp-Sent-Millis: 1465638263698 OkHttp-Received-Millis: 1465638263698 sup, bra? 6 11, 2016 6:44:23 午後 okhttp3.mockwebserver.MockWebServer$4 processOneRequest 情報: MockWebServer[64145] received request: GET /v1/chat/ HTTP/1.1 and responded: HTTP/1.1 200 OK 3 Content-Length: 6 OkHttp-Sent-Millis: 1465638263699 OkHttp-Received-Millis: 1465638263701 yo dog 6 11, 2016 6:44:23 午後 okhttp3.mockwebserver.MockWebServer$3 acceptConnections 情報: MockWebServer[64145] done accepting connections: socket closed |
显然我意识到这并不是在创建简单的Mock入口点并测试收购。
仅执行最低要求的测试
仍然令人困惑,所以我将真正编写一个Hello世界级的UnitTest进行研究。
仅执行最低要求的测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.io.IOException; import org.junit.Test; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; public class MockServerTest { @Test public void test() throws IOException, InterruptedException { final MockWebServer server = new MockWebServer(); server.enqueue(new MockResponse().setBody("hello, world!")); server.start(); final OkHttpClient client = new OkHttpClient(); final Request request = new Request.Builder().url(server.url("/hello")).build(); final Response response = client.newCall(request).execute(); assertEquals("13", response.header("Content-Length")); assertNotNull(response.header("OkHttp-Sent-Millis")); assertNotNull(response.header("OkHttp-Received-Millis")); assertEquals("hello, world!", response.body().string()); server.shutdown(); } } |
我将逐步解释。
MockWebServer
模拟Web服务器。它具有MockResponse的队列,如下所述。
模拟响应
定义模拟响应的对象。它可以具有正文,标题或状态代码。
准备MockWebServer
初始化
MockWebServer对象,然后将Mock响应添加到其队列中。这次,我将简单地将文本打包到正文中。准备好队列后,请调用MockWebServer对象的start方法。
1 2 3 | final MockWebServer server = new MockWebServer(); server.enqueue(new MockResponse().setBody("hello, world!")); server.start(); |
与OkHttpClient
的HTTP通信
下面是几乎可以处理OkHttp的代码。
1 2 3 | final OkHttpClient client = new OkHttpClient(); final Request request = new Request.Builder().url(server.url("/hello")).build(); final Response response = client.newCall(request).execute(); |
使用
MockWebServer对象的url方法发布带有任何路径作为参数传递的临时URL。通过此方法获得的对象是HttpUrl,它是OkHttp特定的类,但是您可以使用toString()获得普通的URL字符串。 HTTP客户端访问临时URL。
发出临时网址
1 | server.url("/hello") // http://localhost:64243/hello |
除非您指定
端口号,否则每个MockWebServer对象的端口号似乎都会改变。
响应操作
响应头
除非另有说明,否则包括以下3个项目。
检查Http响应头
1 2 3 | assertEquals("13", response.header("Content-Length")); assertNotNull(response.header("OkHttp-Sent-Millis")); assertNotNull(response.header("OkHttp-Received-Millis")); |
身体
包含在队列中注册的正文。
身体检查
1 | assertEquals("hello, world!", response.body().string()); |
结束于
关闭使用的MockWebServer对象。
1 | server.shutdown(); |
玩MockResponse
由于它实现了本身返回
的Setter类型,因此可以通过方法链进行连接。我觉得这些东西在最近的图书馆中有所增加。
1 2 3 4 5 6 7 | final MockResponse mock = new MockResponse() .setBody("hello, world!") .setHeader("Mock-Header", "mock") .setHeader("Mock-HeaderB", "mock") .setHeader("Mock-HeaderB", "mocker") .setResponseCode(403) .setResponseCode(404); |
setHeader
使用键值对设置任何标头。如果多次调用,则可以多次注册。如果密钥重复,它将被最后一个值覆盖。在上面的示例中,键= \\" Mock-HeaderB \\"的值将为\\" mocker \\"。
setResponseCode
使用int设置HTTP状态代码。指定的最后一个有效。另外,如果指定3xx系列,4xx系列或5xx系列,则似乎被视为请求失败。
setStatus
您可以设置
状态消息(类似于\\" HTTP / 1.1 200 OK \\"的字符串,该字符串以curl的--head选项开头。如果传递的消息不符合规则,则
邮件设置非法
1 | .setStatus("Rotten Apple Error.") |
发出HTTP请求时发生以下异常。
异常:java.net.ProtocolException
1 2 3 | java.net.ProtocolException: Unexpected status line: Rotten Apple Error. at okhttp3.internal.http.StatusLine.parse(StatusLine.java:69) ……省略…… |
可以注册符合
规则的任何消息,并且状态代码将被覆盖。
合法的邮件设置
1 | .setStatus("HTTP/1.1 666 Rotten Apple Error.") |
查看
实现,似乎是从setResponseCode方法调用的,并且它是内部使用的所有方法。我不确定为什么要公开发布。
setResponseCode
1 2 3 4 | public MockResponse setResponseCode(int code) { /* 省略 */ return setStatus("HTTP/1.1 " + code + " " + reason); } |
使用分派器
GitHub存储库README.md示例显示了一种使用MockResponse队列的格式。当然,队列一旦取出就消失了。如果您使用空队列向MockWebServer发出请求,则该请求将停止直到超时且没有响应。
当我想到"有些不同"时,在README.md的底部引入了Dispatcher。
使用分派器
的整个代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | import java.io.IOException; import org.eclipse.collections.impl.block.factory.Procedures; import org.eclipse.collections.impl.factory.Lists; import org.junit.Test; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; public class MockServerTest { @Test public void test() throws IOException, InterruptedException { final MockWebServer server = new MockWebServer(); final Dispatcher dispatcher = new Dispatcher() { @Override public MockResponse dispatch(final RecordedRequest request) throws InterruptedException { if (request == null || request.getPath() == null) { return new MockResponse().setResponseCode(400); } switch (request.getPath()) { case "/hello": return new MockResponse().setBody("Hello world!").setResponseCode(200); case "/transferred": return new MockResponse().setResponseCode(301); case "/forbidden": return new MockResponse().setResponseCode(403); default: return new MockResponse().setResponseCode(404); } } }; server.setDispatcher(dispatcher); server.start(); final OkHttpClient client = new OkHttpClient(); ArrayAdapter.adapt("/hello", "/transferred", "/forbidden", "/notexists", "/hello") .each( Procedures.throwing(path -> { final HttpUrl url = server.url(path); final Request request = new Request.Builder().url(url).build(); final Response response = client.newCall(request).execute(); System.out.println(url.toString()); System.out.println(response.message()); System.out.println(response.isSuccessful()); System.out.println(response.code()); System.out.println(response.headers()); }) ); server.shutdown(); } } |
执行结果
这样,您将拥有一个MockWebServer,无论您访问相同的URL多??少次并且无法访问不存在的路径,它都可以具有相同的主体。
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | 6 11, 2016 8:21:14 午後 okhttp3.mockwebserver.MockWebServer$3 execute 情報: MockWebServer[58567] starting to accept connections 6 11, 2016 8:21:15 午後 okhttp3.mockwebserver.MockWebServer$4 processOneRequest 情報: MockWebServer[58567] received request: GET /hello HTTP/1.1 and responded: HTTP/1.1 200 OK http://localhost:58567/hello OK true 200 Content-Length: 12 OkHttp-Sent-Millis: 1465644075148 OkHttp-Received-Millis: 1465644075154 http://localhost:58567/transferred Redirection false 301 Content-Length: 0 OkHttp-Sent-Millis: 1465644075158 OkHttp-Received-Millis: 1465644075159 6 11, 2016 8:21:15 午後 okhttp3.mockwebserver.MockWebServer$4 processOneRequest 情報: MockWebServer[58567] received request: GET /transferred HTTP/1.1 and responded: HTTP/1.1 301 Redirection http://localhost:58567/forbidden Client Error false 403 Content-Length: 0 OkHttp-Sent-Millis: 1465644075161 OkHttp-Received-Millis: 1465644075163 6 11, 2016 8:21:15 午後 okhttp3.mockwebserver.MockWebServer$4 processOneRequest 情報: MockWebServer[58567] received request: GET /forbidden HTTP/1.1 and responded: HTTP/1.1 403 Client Error http://localhost:58567/notexists Client Error false 404 Content-Length: 0 OkHttp-Sent-Millis: 1465644075163 OkHttp-Received-Millis: 1465644075164 6 11, 2016 8:21:15 午後 okhttp3.mockwebserver.MockWebServer$4 processOneRequest 情報: MockWebServer[58567] received request: GET /notexists HTTP/1.1 and responded: HTTP/1.1 404 Client Error http://localhost:58567/hello OK true 200 Content-Length: 12 OkHttp-Sent-Millis: 1465644075165 OkHttp-Received-Millis: 1465644075165 6 11, 2016 8:21:15 午後 okhttp3.mockwebserver.MockWebServer$4 processOneRequest 情報: MockWebServer[58567] received request: GET /hello HTTP/1.1 and responded: HTTP/1.1 200 OK 6 11, 2016 8:21:15 午後 okhttp3.mockwebserver.MockWebServer$3 acceptConnections 情報: MockWebServer[58567] done accepting connections: socket closed |
下面,我们将详细介绍。
导入
注意,
OkHttp本身也有一个同名的类。
1 | import okhttp3.mockwebserver.Dispatcher; |
分派器
的定义
MockResponse可以分发到指定的路径。
Dispatcher是一个抽象类,您需要实现dispatch方法。分派方法将RecordedRequest对象的请求作为参数,查看请求的Path,并分离返回的MockResponse。由于它不是队列,因此无论访问同一路径多少次,它都会返回相同的MockResponse。
调度员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | final Dispatcher dispatcher = new Dispatcher() { @Override public MockResponse dispatch(final RecordedRequest request) throws InterruptedException { if (request == null || request.getPath() == null) { return new MockResponse().setResponseCode(400); } switch (request.getPath()) { case "/hello": return new MockResponse().setBody("Hello world!").setResponseCode(200); case "/transferred": return new MockResponse().setResponseCode(301); case "/forbidden": return new MockResponse().setResponseCode(403); default: return new MockResponse().setResponseCode(404); } } }; |
如果您使用的是Java SE7或更高版本,则可以对String使用切换大小写。但是,如果在switch评估的表达式中包含null((的内容)),则会发生NullPointerException,因此您必须编写一个null检查。
调度程序设置
将MockWebServer对象设置为您刚看到的Dispatcher。
1 | server.setDispatcher(dispatcher); |
请求
依次向以下路径发出HTTP请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ArrayAdapter.adapt("/hello", "/transferred", "/forbidden", "/notexists", "/hello") .each( Procedures.throwing(path -> { final HttpUrl url = server.url(path); final Request request = new Request.Builder().url(url).build(); final Response response = client.newCall(request).execute(); System.out.println(url.toString()); System.out.println(response.message()); System.out.println(response.isSuccessful()); System.out.println(response.code()); System.out.println(response.headers()); }) ); |
未在Dispatcher中设置为分发的路径/注释者将为404。
您也可以通过两次调用/ hello获得相同的主体
概括
MockWebServer是一个库,可用于编写HTTP客户端和HTTP连接的单元测试。对于正常使用,最好定义一个Dispatcher,将其设置在MockWebServer对象中,然后启动它,并为HTTP连接编写测试代码。
参考