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_STATE
到 THIRD_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 更简单粗暴。