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());