使用OkHttp3的MockWebServer


概述

我搜索了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

GoogleTrend_MockWebServer.png

好的Http

GoogleTrend_OkHttp.png

开始使用

build.gradle

您可以通过添加一个

依赖项来使用它。请注意,OkHttp包含在依赖项中。

build.gradle

1
2
3
4
dependencies {
......
    testCompile 'com.squareup.okhttp3:mockwebserver:3.2.0'
}

(可选)Eclipse集合

添加了Eclipse Collections以使用以下类。它不是必需的,但很有用。

  • 间隔(Java SE8 IntStream的便利)
  • Procedures#throwing(我不必在Lambda表达式中编写Try-Catch,因此我希望不必加深嵌套的视觉效果)
  • 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个项目。

  • 内容长度
  • OkHttp发送Millis
  • OkHttp收到的Millis
  • 检查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连接编写测试代码。

    参考

  • MockWebServer(GitHub存储库)
  • 将OkHttp的MockWebServer与JUnit一起使用
  • HTTP状态码