1. 概述

这篇快速教程将介绍 如何使用 WireMock 来测试具有状态变化行为的 HTTP API

如果你对 WireMock 还不太熟悉,建议先阅读我们之前的入门文章 Introduction to WireMock

2. Maven 依赖配置

为了使用 WireMock 库,我们需要在项目的 pom.xml 中添加以下依赖项:

<dependency>
    <groupId>org.wiremock</groupId>
    <artifactId>wiremock</artifactId>
    <version>3.9.1</version>
    <scope>test</scope>
</dependency>

3. 待模拟的示例 API

WireMock 中的 Scenarios(场景)机制用于模拟 REST API 的不同状态,这使得我们可以编写测试用例,来验证目标 API 在不同状态下返回不同的响应内容。

举个例子:我们有一个“Java 小贴士”服务,每次请求其 /java-tip 接口时会返回一条不同的 Java 编程技巧。

比如第一次调用返回:

"use composition rather than inheritance"

再次调用则会返回另一条提示。

4. 创建场景状态

我们需要为 /java-tip 接口创建多个 stub,每个 stub 对应一个特定的状态,并返回对应的内容。下面是完整的测试类示例:

public class WireMockScenarioExampleIntegrationTest {
    private static final String THIRD_STATE = "third";
    private static final String SECOND_STATE = "second";
    private static final String TIP_01 = "finally block is not called when System.exit()" 
      + " is called in the try block";
    private static final String TIP_02 = "keep your code clean";
    private static final String TIP_03 = "use composition rather than inheritance";
    private static final String TEXT_PLAIN = "text/plain";
    
    static int port = 9999;
    
    @Rule
    public WireMockRule wireMockRule = new WireMockRule(port);    

    @Test
    public void changeStateOnEachCallTest() throws IOException {
        createWireMockStub(Scenario.STARTED, SECOND_STATE, TIP_01);
        createWireMockStub(SECOND_STATE, THIRD_STATE, TIP_02);
        createWireMockStub(THIRD_STATE, Scenario.STARTED, TIP_03);
        
    }

    private void createWireMockStub(String currentState, String nextState, String responseBody) {
        stubFor(get(urlEqualTo("/java-tip"))
          .inScenario("java tips")
          .whenScenarioStateIs(currentState)
          .willSetStateTo(nextState)
          .willReturn(aResponse()
            .withStatus(200)
            .withHeader("Content-Type", TEXT_PLAIN)
            .withBody(responseBody)));
    }

}

在这个类中,我们使用了 WireMock 提供的 JUnit Rule 类 WireMockRule,它会在测试运行时自动启动一个 WireMock 服务。

我们通过 stubFor() 方法创建多个 stub,关键点在于以下两个方法:

  • whenScenarioStateIs: 指定当前场景必须处于某个状态才会命中这个 stub
  • willSetStateTo: 表示该 stub 被调用后,会将场景状态切换为指定值

WireMock 场景的初始状态是 Scenario.STARTED。因此我们首先定义当状态为 Scenario.STARTED 时返回第一条 tip,并将状态更新为 SECOND_STATE

接着我们继续添加从 SECOND_STATETHIRD_STATE,再从 THIRD_STATE 回到 Scenario.STARTED 的 stub,形成一个循环:

Scenario.STARTED -> SECOND_STATE -> THIRD_STATE -> Scenario.STARTED

5. 使用场景进行测试

要实际验证场景是否生效,我们需要重复调用 /java-tip 接口。为此我们扩展测试方法如下:

    @Test
    public void changeStateOnEachCallTest() throws IOException {
        createWireMockStub(Scenario.STARTED, SECOND_STATE, TIP_01);
        createWireMockStub(SECOND_STATE, THIRD_STATE, TIP_02);
        createWireMockStub(THIRD_STATE, Scenario.STARTED, TIP_03);

        assertEquals(TIP_01, nextTip());
        assertEquals(TIP_02, nextTip());
        assertEquals(TIP_03, nextTip());
        assertEquals(TIP_01, nextTip());        
    }

    private String nextTip() throws ClientProtocolException, IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet request = new HttpGet(String.format("http://localhost:%s/java-tip", port));
        HttpResponse httpResponse = httpClient.execute(request);
        return firstLineOfResponse(httpResponse);
    }

    private static String firstLineOfResponse(HttpResponse httpResponse) throws IOException {
        try (BufferedReader reader = new BufferedReader(
          new InputStreamReader(httpResponse.getEntity().getContent()))) {
            return reader.readLine();
        }
    }

其中:

  • nextTip() 方法负责向 /java-tip 发起请求并获取响应的第一行内容。
  • 每次调用后通过 assertEquals() 验证是否按预期顺序返回不同的 tip。

这样我们就完成了基于场景的状态流转模拟测试。

6. 总结

本文展示了如何使用 WireMock 的 Scenarios 功能,来模拟那些根据自身状态返回不同响应的 API。这种机制非常适合测试一些带有状态逻辑的服务行为,比如分页、流程状态变更等场景。

⚠️ 注意事项:

  • 场景状态由 WireMock 内部维护,不支持跨测试共享。
  • 建议给每个场景命名清晰,避免混淆。
  • 若 stub 数量较多,注意初始化顺序和状态流转逻辑的一致性。

✅ 实战建议:

  • 在集成测试中合理利用 Scenarios 可以大大简化 mock 行为的复杂度。
  • 避免滥用,对于无状态的接口还是直接 stub 更简单粗暴。

原始标题:Using WireMock Scenarios | Baeldung