2. Maven 依赖

要在项目中使用 WireMock,需要在 POM 文件中添加以下依赖:

<dependency>
    <groupId>org.wiremock</groupId>
    <artifactId>wiremock</artifactId>
    <version>3.3.1</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. 基础用法

演示一个最简单的存根配置(精确匹配 URL):

  1. 创建服务器实例:

    WireMockServer wireMockServer = new WireMockServer();
    
  2. 启动服务器(客户端连接前必须启动):

    wireMockServer.start();
    
  3. 配置存根:

    configureFor("localhost", 8080);
    stubFor(get(urlEqualTo("/baeldung"))
        .willReturn(aResponse()
            .withBody("Welcome to Baeldung!")));
    
  4. 创建 HTTP 客户端(使用 Apache HttpClient):

    CloseableHttpClient httpClient = HttpClients.createDefault();
    
  5. 执行请求并获取响应:

    HttpGet request = new HttpGet("http://localhost:8080/baeldung");
    HttpResponse httpResponse = httpClient.execute(request);
    
  6. 响应体转字符串(辅助方法):

    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;
    }
    
  7. 验证行为:

    verify(getRequestedFor(urlEqualTo("/baeldung"))); // 验证请求URL
    assertEquals("Welcome to Baeldung!", responseString); // 验证响应体
    
  8. 释放资源:

    wireMockServer.stop();
    

4. JUnit 管理服务器

本节展示如何使用 JUnit Rule 自动管理 WireMock 服务器生命周期。

4.1. 服务器设置

通过 @Rule 注解集成 JUnit:

  • JUnit 自动在测试方法前启动服务器,方法后停止服务器
  • 可指定端口(未指定时默认 8080
@Rule
public WireMockRule wireMockRule = new WireMockRule(int port);

提示:可通过 Options 接口配置主机(默认 localhost)等参数。

4.2. URL 匹配

使用正则表达式匹配接口路径:

  1. 配置存根:

    stubFor(get(urlPathMatching("/baeldung/.*"))
        .willReturn(aResponse()
            .withStatus(200)
            .withHeader("Content-Type", "application/json")
            .withBody("\"testing-library\": \"WireMock\"")));
    
  2. 执行请求:

    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpGet request = new HttpGet("http://localhost:8080/baeldung/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;
    }
    
  3. 验证结果:

    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. 请求头匹配

演示基于请求头的存根配置:

  1. 配置存根(匹配 Accept 头):

    stubFor(get(urlPathEqualTo("/baeldung/wiremock"))
        .withHeader("Accept", matching("text/.*"))
        .willReturn(aResponse()
            .withStatus(503)
            .withHeader("Content-Type", "text/html")
            .withBody("!!! Service Unavailable !!!")));
    
  2. 执行请求(添加请求头):

    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);
    
  3. 验证结果:

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

4.4. 请求体匹配

演示基于请求体的存根配置:

  1. 配置存根(匹配 JSON 请求体):

    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)));
    
  2. 准备请求体(从 classpath 加载 JSON):

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

    wiremock_intro.json 内容:

    {
        "testing-library": "WireMock",
        "creator": "Tom Akehurst",
        "website": "wiremock.org"
    }
    
  3. 执行请求:

    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);
    
  4. 验证结果:

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

4.5. 存根优先级

处理多个存根匹配同一请求的场景:

踩坑提示:默认情况下,最近添加的存根优先级最高。可通过 atPriority() 覆盖此行为(数值越小优先级越高)。

场景一:未设置优先级

  1. 配置两个存根:

    stubFor(get(urlPathMatching("/baeldung/.*"))
        .willReturn(aResponse().withStatus(200)));
    
    stubFor(get(urlPathEqualTo("/baeldung/wiremock"))
        .withHeader("Accept", matching("text/.*"))
        .willReturn(aResponse().withStatus(503)));
    
  2. 执行请求(使用辅助方法):

    HttpResponse httpResponse = generateClientAndReceiveResponseForPriorityTests();
    

    辅助方法:

    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);
    }
    
  3. 验证结果(后配置的存根生效):

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

场景二:设置优先级

  1. 配置带优先级的存根:

    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)));
    
  2. 执行请求(同场景一):

    HttpResponse httpResponse = generateClientAndReceiveResponseForPriorityTests();
    
  3. 验证结果(高优先级存根生效):

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

5. 总结

本文介绍了 WireMock 的核心功能,包括:

  • 手动/JUnit 两种服务器管理方式
  • URL/请求头/请求体匹配技巧
  • 存根优先级控制机制

通过这些技术,可以高效构建 REST API 测试的模拟服务。完整示例代码可在 GitHub 项目 中获取。


原始标题:Introduction to WireMock | Baeldung