1. 概述
WireMock 是一個用於創建樁(stub)和模擬(mock)Web 服務庫。它構建了一個 HTTP 服務器,我們可以像連接到實際 Web 服務一樣連接到它。
當 WireMock 服務器運行時,我們可以設置期望,調用服務並驗證其行為。
2. Maven 依賴
為了充分利用 WireMock 庫,我們需要在 POM 中添加 這個依賴:
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>3.9.1</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 的樁,無需進行任何其他配置。
讓我們創建一個服務器實例:
WireMockServer wireMockServer = new WireMockServer();WireMock 服務器必須在客户端連接之前運行:
wireMockServer.start();服務端接口被封裝成樁接口:
configureFor("localhost", 8080);
stubFor(get(urlEqualTo("/baeldung")).willReturn(aResponse().withBody("Welcome to Baeldung!")));本教程使用 Apache HttpClient API 來表示客户端連接到服務器的方式。
CloseableHttpClient httpClient = HttpClients.createDefault();請求被執行,並在之後返回響應:
HttpGet request = new HttpGet("http://localhost:8080/baeldung");
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("/baeldung")));
assertEquals("Welcome to Baeldung!", stringResponse);最後,我們需要停止 WireMock 服務器以釋放系統資源:
wireMockServer.stop();4. JUnit 管理服務器
本節演示了使用 JUnit 5 中的 WireMock 服務器的方法。 在 JUnit 5 中,JUnit 4 中使用的 @Rule 註解被生命週期方法(如 @BeforeEach 和 @AfterEach)或自定義擴展所取代。
4.1. 服務器設置
通過使用生命週期方法,可以在 JUnit 測試用例中管理 WireMock 服務器的生命週期,確保服務器在每個測試前啓動,並在測試完成後停止。
類似於通過編程方式管理的服務器,我們創建一個 WireMock 服務器作為 Java 對象:
private WireMockServer wireMockServer;
@BeforeEach
void setup() {
wireMockServer = new WireMockServer(WireMockConfiguration.options().port(port));
wireMockServer.start();
WireMock.configureFor("localhost", port);
}
@AfterEach
void teardown() {
if (wireMockServer != null) {
wireMockServer.stop();
}
}如果未指定端口,服務器端口將默認為 8080。 服務器主機,默認為 localhost,以及其他配置均可通過 Options 界面指定。
4.2 URL 匹配
在配置 WireMock 服務器實例後,下一步是配置樁。
在本節中,我們將使用正則表達式為服務端點配置一個 REST 樁。
stubFor(get(urlPathMatching("/baeldung/.*"))
.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/baeldung/wiremock");
HttpResponse httpResponse = httpClient.execute(request);
String stringResponse = convertHttpResponseToString(httpResponse);上面的代碼片段使用了一個輔助方法將 HttpResponse 轉換為 String:
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("/baeldung/wiremock")));
assertEquals(200, httpResponse.getStatusLine().getStatusCode());
assertEquals("application/json", httpResponse.getFirstHeader("Content-Type").getValue());
assertEquals("\"testing-library\": \"WireMock\"", stringResponse);4.3. 請求頭匹配
現在我們將演示如何通過匹配請求頭來 stub 一個 REST API。
讓我們從 stub 配置開始:
stubFor(get(urlPathEqualTo("/baeldung/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/baeldung/wiremock");
request.addHeader("Accept", "text/html");
HttpResponse httpResponse = httpClient.execute(request);
String stringResponse = convertHttpResponseToString(httpResponse);以下驗證和斷言確認我們之前創建的樁函數的有效性:
verify(getRequestedFor(urlEqualTo("/baeldung/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("/baeldung/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。
以下是 classpath 上 wiremock_intro.json 文件的內容:
{
"testing-library": "WireMock",
"creator": "Tom Akehurst",
"website": "wiremock.org"
}我們可以配置和運行 HTTP 請求和響應:
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost request = new HttpPost("http://localhost:8080/baeldung/wiremock");
request.addHeader("Content-Type", "application/json");
request.setEntity(entity);
HttpResponse response = httpClient.execute(request);這是用於驗證樁代碼的測試代碼:
verify(postRequestedFor(urlEqualTo("/baeldung/wiremock"))
.withHeader("Content-Type", equalTo("application/json")));
assertEquals(200, response.getStatusLine().getStatusCode());<h3><strong>4.5. Stub 優先級</strong></h3>
<p>前幾節討論了當 HTTP 請求僅匹配單個 Stub 的情況。</p>
<p>如果存在多個 Stub 與請求匹配,則情況會更加複雜。 默認情況下,在這種情況下,最後添加的 Stub 將具有優先權。</p>
<p>但是,用户可以自定義此行為,從而更好地控制 WireMock Stub。</p>
<p>我們將演示當傳入的請求同時匹配兩個不同的 Stub,一個設置了優先級,另一個沒有設置優先級的 WireMock 服務器的操作。</p>
<p>這兩個場景將使用以下私有輔助方法:</p>
private HttpResponse generateClientAndReceiveResponseForPriorityTests() throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet("http://localhost:8080/baeldung/wiremock");
request.addHeader("Accept", "text/xml");
return httpClient.execute(request);
}首先,我們配置了兩個樁,不考慮優先級級別:
stubFor(get(urlPathMatching("/baeldung/.*"))
.willReturn(aResponse()
.withStatus(200)));
stubFor(get(urlPathEqualTo("/baeldung/wiremock"))
.withHeader("Accept", matching("text/.*"))
.willReturn(aResponse()
.withStatus(503)));接下來,我們創建一個 HTTP 客户端並使用助手方法執行請求:
HttpResponse httpResponse = generateClientAndReceiveResponseForPriorityTests();以下代碼片段驗證,無論之前配置的 stub 是否存在,只要請求與兩者都匹配,則都會應用最後配置的 stub。
verify(getRequestedFor(urlEqualTo("/baeldung/wiremock")));
assertEquals(503, httpResponse.getStatusLine().getStatusCode());讓我們繼續討論帶有優先級設定的 stub,其中較低的數字表示更高的優先級:
stubFor(get(urlPathMatching("/baeldung/.*"))
.atPriority(1)
.willReturn(aResponse()
.withStatus(200)));
stubFor(get(urlPathEqualTo("/baeldung/wiremock"))
.atPriority(2)
.withHeader("Accept", matching("text/.*"))
.willReturn(aResponse()
.withStatus(503)));現在我們將執行 HTTP 請求的創建和執行。
HttpResponse httpResponse = generateClientAndReceiveResponseForPriorityTests();以下代碼驗證了優先級級別的效果,優先應用配置的第一個樁,而不是最後一個:
verify(getRequestedFor(urlEqualTo("/baeldung/wiremock")));
assertEquals(200, httpResponse.getStatusLine().getStatusCode());5. 結論
本文介紹了 WireMock 的使用及其在測試 REST API 方面的設置和配置方法,包括通過 URL、請求頭和請求體進行匹配等多種技術。