知識庫 / Testing RSS 訂閱

WireMock 入門

REST,Testing
HongKong
11
03:56 AM · Dec 06 ,2025

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、請求頭和請求體進行匹配等多種技術。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.