Contents

Wiremock简介

1. 概述

WireMock是一个用于存根和模拟 Web 服务的库。它构建了一个 HTTP 服务器,我们可以像连接到实际的 Web 服务一样连接到该服务器。

WireMock 服务器运行时,我们可以设置期望,调用服务,然后验证其行为。

2. Maven依赖

为了能够利用 WireMock 库,我们需要在 POM 中包含以下依赖项:

<dependency>
    <groupId>com.github.tomakehurst</groupId>
    <artifactId>wiremock</artifactId>
    <version>1.58</version>
    <scope>test</scope>
</dependency>

3. 程序化管理的服务器

本节将介绍手动配置 WireMock 服务器的方法。即没有JUnit 自动配置的支持。用法由一个非常简单的存根演示。

3.1. 服务器设置

可以像这样实例化 WireMock 服务器:

WireMockServer wireMockServer = new WireMockServer(String host, int port);

如果未提供任何参数,则服务器主机默认为localhost,服务器端口默认为8080

然后可以使用两种简单的方法启动和停止服务器:

wireMockServer.start();

和:

wireMockServer.stop();

3.2. 基本用法

WireMock 库将首先通过基本用法进行演示,其中提供了无需任何进一步配置的精确 URL 的存根。让我们创建一个服务器实例:

WireMockServer wireMockServer = new WireMockServer();

WireMock 服务器必须在客户端连接到它之前运行:

wireMockServer.start();

然后对 Web 服务进行存根:

configureFor("localhost", 8080);
stubFor(get(urlEqualTo("/blogdemo")).willReturn(aResponse().withBody("Welcome to Blogdemo!")));

本教程使用 Apache HttpClient API 来表示连接到服务器的客户端:

CloseableHttpClient httpClient = HttpClients.createDefault();

之后分别执行请求并返回响应:

HttpGet request = new HttpGet("http://localhost:8080/blogdemo");
HttpResponse httpResponse = httpClient.execute(request);

我们将使用辅助方法将httpResponse变量转换为String

String responseString = convertResponseToString(httpResponse);

这是该转换辅助方法的实现:

private String convertResponseToString(HttpResponse response) throws IOException {
    InputStream responseStream = response.getEntity().getContent();
    Scanner scanner = new Scanner(responseStream, "UTF-8");
    String responseString = scanner.useDelimiter("\\Z").next();
    scanner.close();
    return responseString;
}

以下代码验证服务器是否收到了对预期 URL 的请求,并且到达客户端的响应正是发送的内容:

verify(getRequestedFor(urlEqualTo("/blogdemo")));
assertEquals("Welcome to Blogdemo!", stringResponse);

最后,应停止 WireMock 服务器以释放系统资源:

wireMockServer.stop();

4. JUnit 托管服务器

与第 3 节相比,本节说明了在 JUnit Rule的帮助下使用 WireMock 服务器。

4.1. 服务器设置

可以使用*@Rule*注解将 WireMock 服务器集成到 JUnit 测试用例中。这允许 JUnit 管理生命周期,在每个测试方法之前启动服务器并在方法返回后停止它。

与以编程方式管理的服务器类似,可以将 JUnit 管理的 WireMock 服务器创建为具有给定端口号的 Java 对象:

@Rule
public WireMockRule wireMockRule = new WireMockRule(int port);

如果没有提供参数,服务器端口将采用默认值8080。服务器主机,默认为localhost和其他配置可以使用选项接口指定。

4.2. 网址匹配

设置WireMockRule实例后,下一步是配置存根。在本小节中,我们将使用正则表达式为服务端点提供 REST 存根:

stubFor(get(urlPathMatching("/blogdemo/.*"))
  .willReturn(aResponse()
  .withStatus(200)
  .withHeader("Content-Type", "application/json")
  .withBody("\"testing-library\": \"WireMock\"")));

让我们继续创建 HTTP 客户端,执行请求并接收响应:

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet("http://localhost:8080/blogdemo/wiremock");
HttpResponse httpResponse = httpClient.execute(request);
String stringResponse = convertHttpResponseToString(httpResponse);

上面的代码片段利用了一个转换辅助方法:

private String convertHttpResponseToString(HttpResponse httpResponse) throws IOException {
    InputStream inputStream = httpResponse.getEntity().getContent();
    return convertInputStreamToString(inputStream);
}

这又使用了另一种私有方法:

private String convertInputStreamToString(InputStream inputStream) {
    Scanner scanner = new Scanner(inputStream, "UTF-8");
    String string = scanner.useDelimiter("\\Z").next();
    scanner.close();
    return string;
}

存根的操作由下面的测试代码验证:

verify(getRequestedFor(urlEqualTo("/blogdemo/wiremock")));
assertEquals(200, httpResponse.getStatusLine().getStatusCode());
assertEquals("application/json", httpResponse.getFirstHeader("Content-Type").getValue());
assertEquals("\"testing-library\": \"WireMock\"", stringResponse);

4.3. 请求头匹配

现在我们将演示如何使用匹配的标头来存根 REST API。让我们从存根配置开始:

stubFor(get(urlPathEqualTo("/blogdemo/wiremock"))
  .withHeader("Accept", matching("text/.*"))
  .willReturn(aResponse()
  .withStatus(503)
  .withHeader("Content-Type", "text/html")
  .withBody("!!! Service Unavailable !!!")));

与上一小节类似,我们使用 HttpClient API 说明 HTTP 交互,并借助相同的辅助方法:

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet("http://localhost:8080/blogdemo/wiremock");
request.addHeader("Accept", "text/html");
HttpResponse httpResponse = httpClient.execute(request);
String stringResponse = convertHttpResponseToString(httpResponse);

以下验证和断言确认了我们之前创建的存根的功能:

verify(getRequestedFor(urlEqualTo("/blogdemo/wiremock")));
assertEquals(503, httpResponse.getStatusLine().getStatusCode());
assertEquals("text/html", httpResponse.getFirstHeader("Content-Type").getValue());
assertEquals("!!! Service Unavailable !!!", stringResponse);

4.4. 请求正文匹配

WireMock 库也可用于存根带有正文匹配的 REST API。这是此类存根的配置:

stubFor(post(urlEqualTo("/blogdemo/wiremock"))
  .withHeader("Content-Type", equalTo("application/json"))
  .withRequestBody(containing("\"testing-library\": \"WireMock\""))
  .withRequestBody(containing("\"creator\": \"Tom Akehurst\""))
  .withRequestBody(containing("\"website\": \"wiremock.org\""))
  .willReturn(aResponse()
  .withStatus(200)));

现在,是时候创建一个用作请求主体的StringEntity对象了:

InputStream jsonInputStream 
  = this.getClass().getClassLoader().getResourceAsStream("wiremock_intro.json");
String jsonString = convertInputStreamToString(jsonInputStream);
StringEntity entity = new StringEntity(jsonString);

上面的代码使用之前定义的转换辅助方法之一convertInputStreamToString

这是类路径上的wiremock_intro.json文件的内容:

{
    "testing-library": "WireMock",
    "creator": "Tom Akehurst",
    "website": "wiremock.org"
}

HTTP 请求和响应可以按如下方式配置和执行:

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost request = new HttpPost("http://localhost:8080/blogdemo/wiremock");
request.addHeader("Content-Type", "application/json");
request.setEntity(entity);
HttpResponse response = httpClient.execute(request);

这是用于验证存根的测试代码:

verify(postRequestedFor(urlEqualTo("/blogdemo/wiremock"))
  .withHeader("Content-Type", equalTo("application/json")));
assertEquals(200, response.getStatusLine().getStatusCode());

4.5. 存根优先级

前面的小节处理 HTTP 请求仅匹配单个存根的情况。如果请求的匹配项不止一个,情况会更复杂。默认情况下,在这种情况下,最近添加的存根将优先。但是,允许用户自定义该行为以更好地控制 WireMock 存根。

我们将演示当传入的请求同时匹配两个不同的存根(有和没有设置优先级)时 WireMock 服务器的操作。这两种情况都将使用以下私有辅助方法:

private HttpResponse generateClientAndReceiveResponseForPriorityTests() throws IOException {
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpGet request = new HttpGet("http://localhost:8080/blogdemo/wiremock");
    request.addHeader("Accept", "text/xml");
    return httpClient.execute(request);
}

首先,在不考虑优先级的情况下配置两个存根:

stubFor(get(urlPathMatching("/blogdemo/.*"))
  .willReturn(aResponse()
  .withStatus(200)));
stubFor(get(urlPathEqualTo("/blogdemo/wiremock"))
  .withHeader("Accept", matching("text/.*"))
  .willReturn(aResponse()
  .withStatus(503)));

接下来,创建一个 HTTP 客户端并使用上面描述的辅助方法执行请求:

HttpResponse httpResponse = generateClientAndReceiveResponseForPriorityTests();

以下代码片段验证是否应用了最后一个配置的存根,无论之前定义的存根是什么,当请求都匹配它们时:

verify(getRequestedFor(urlEqualTo("/blogdemo/wiremock")));
assertEquals(503, httpResponse.getStatusLine().getStatusCode());

让我们继续讨论设置了优先级的存根,其中较低的数字表示较高的优先级:

stubFor(get(urlPathMatching("/blogdemo/.*"))
  .atPriority(1)
  .willReturn(aResponse()
  .withStatus(200)));
stubFor(get(urlPathEqualTo("/blogdemo/wiremock"))
  .atPriority(2)
  .withHeader("Accept", matching("text/.*"))
  .willReturn(aResponse()
  .withStatus(503)));

HTTP请求的创建和执行:

HttpResponse httpResponse = generateClientAndReceiveResponseForPriorityTests();

以下代码验证优先级的影响,其中应用了第一个配置的存根而不是最后一个:

verify(getRequestedFor(urlEqualTo("/blogdemo/wiremock")));
assertEquals(200, httpResponse.getStatusLine().getStatusCode());