使用WireMock Scenarios

2023/05/09

1. 概述

WireMock是一个用于stubbing和mocking 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自动配置。我们用一个非常简单的stub来演示用法。

3.1 服务器设置

首先,我们实例化一个WireMock服务器:

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

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

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

wireMockServer.start();

和:

wireMockServer.stop();

3.2 基本用法

我们将首先演示WireMock库的基本用法,其中提供了无需任何进一步配置的确切URL的stub。

让我们创建一个服务器实例:

WireMockServer wireMockServer = new WireMockServer();

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

wireMockServer.start();

然后对Web服务进行stubbed:

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

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

CloseableHttpClient httpClient = HttpClients.createDefault();

执行请求,然后返回响应:

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

我们将使用工具方法将httpResponse变量转换为字符串:

String responseString = convertResponseToString(httpResponse);

以下是该转换工具方法的实现:

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

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

verify(getRequestedFor(urlEqualTo("/tuyucheng")));
assertEquals("Welcome to Tuyucheng!", 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和其他配置可以使用Options接口指定。

4.2 URL匹配

设置WireMockRule实例后,下一步是配置stub。

在本小节中,我们将使用正则表达式为服务端点提供REST stub:

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

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

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet(String.format("http://localhost:%s/tuyucheng/wiremock", port));
HttpResponse httpResponse = httpClient.execute(request);
String stringResponse = convertHttpResponseToString(httpResponse);

上面的代码片段使用了转换工具方法:

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

而该工具方法又使用了另一个私有方法:

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

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

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

4.3 请求头匹配

现在我们将演示如何通过标头匹配对REST API进行stub。

首先我们从stub配置开始:

stubFor(get(urlPathEqualTo("/tuyucheng/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(String.format("http://localhost:%s/tuyucheng/wiremock", port));
request.addHeader("Accept", "text/html");
HttpResponse httpResponse = httpClient.execute(request);
String stringResponse = convertHttpResponseToString(httpResponse);

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

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

4.4 请求正文匹配

我们还可以使用WireMock库来stub具有正文匹配的REST API。

这是此类stub的配置:

stubFor(post(urlEqualTo("/tuyucheng/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。

以下是src/test/resources目录中wiremock_intro.json文件的内容:

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

我们可以配置和运行HTTP请求和响应:

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost request = new HttpPost(String.format("http://localhost:%s/tuyucheng/wiremock", port));
request.addHeader("Content-Type", APPLICATION_JSON);
request.setEntity(entity);
HttpResponse response = httpClient.execute(request);

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

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

4.5 stub优先级

前面的小节处理HTTP请求仅匹配单个stub的情况。

如果请求的匹配项不止一个,则情况会更加复杂。默认情况下,在这种情况下,最近添加的stub将优先。

但是,用户可以自定义该行为以更好地控制WireMock stub。

我们将演示当传入的请求同时匹配两个不同的stub(同时设置和不设置优先级)时WireMock服务器的操作。

这两种情况都将使用以下私有的工具方法:

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

首先,我们在不考虑优先级的情况下配置两个stub:

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

接下来,我们创建一个HTTP客户端并使用工具方法执行请求:

HttpResponse httpResponse = generateClientAndReceiveResponseForPriorityTests();

以下代码片段验证当请求与这两个stub都匹配时,是否应用了最后一个配置的stub,而不考虑之前定义的stub:

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

下面我们继续演示设置了优先级的stub,其中atPriority方法中指定的数字越小表示优先级越高:

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

现在我们将执行HTTP请求的创建和执行:

HttpResponse httpResponse = generateClientAndReceiveResponseForPriorityTests();

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

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

5. 总结

本文介绍了WireMock以及如何设置和配置此库以使用各种技术测试REST API,包括匹配URL、请求头和正文。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章