微服务架构允许我们独立开发、测试和部署一个应用程序的不同组件。虽然这样的组件可以独立开发,但孤立地进行测试是具有挑战性的。对于微服务的真正集成测试,我们必须测试它与其他API的交互。
当我们需要模拟外部API来测试依赖于这些API完成交易的特定API时,WireMock有助于集成测试。WireMock是一个流行的HTTP模拟服务器,有助于模拟API和存根响应。
值得一提的是,WireMock可以作为应用程序的一部分运行,也可以作为一个独立的进程。
1.Maven的依赖性
首先要把wiremock的依赖项导入项目中。我们可以在Maven repo中找到其最新版本。
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.33.2</version>
<scope>test</scope>
</dependency>
2.引导WireMock
有几种方法可以开始使用wiremock。让我们来看看它们。
2.1.使用WireMockServer
创建WireMockServer实例的最简单方法是调用其构造函数。**默认情况下,wiremock使用主机名localhost 和端口号8080 。**我们可以使用configureFor() ,用一个随机/固定的端口号和一个自定义的主机名来初始化一个WireMockServer 。
在测试执行前启动服务器,并在测试结束后停止服务器是非常重要的。我们可以在测试之间重置模拟存根。
下面是一个用JUnit 5测试设置wiremock的例子。请注意,这种技术也可以用在独立的Java应用程序中。它不只限于测试。
public class WireMockServerTest {
static WireMockServer wireMockServer = new WireMockServer();
@BeforeAll
public static void beforeAll() {
//WireMock.configureFor("custom-host", 9000, "/api-root-url");
wireMockServer.start();
}
@AfterAll
public static void afterAll() {
wireMockServer.stop();
}
@AfterEach
public void afterEach() {
wireMockServer.resetAll();
}
}
2.2.使用WireMockRule
在JUnit 4测试中,WireMockRule是配置、启动和停止服务器的首选方式,尽管我们也可以在JUnit 5测试中使用它。它在功能和控制方面与WireMockServer类非常相似。
下面是一个用JUnit 4测试设置Wiremock的例子。
public class WireMockServerTest {
@Rule
WireMockRule wireMockRule = new WireMockRule();
@Before
public void beforeAll() {
wireMockRule.start();
}
@After
public void afterAll() {
wireMockRule.stop();
}
@AfterEach
public void afterEach() {
wireMockRule.resetAll();
}
}
2.3.使用*@WireMockTest*
@WireMockTest注解是另一种使用wiremock为JUnit测试提供动力的便捷方式。这是一个类级注解。
@WireMockTest在测试开始前启动wiremock服务器,在测试结束后停止服务器,并在测试之间清理上下文。所以基本上,它隐含地完成了我们在前几节中使用前后注解所做的所有三个步骤。
@WireMockTest
public class WireMockTestAnnotationTest {
//...
}
2.4.启用HTTPS
我们可以通过httpsEnabled注解参数来启用HTTPS。默认情况下,一个随机的端口将被分配。要固定HTTPS的端口号,请使用httpsPort参数。
@WireMockTest(httpsEnabled = true, httpsPort = 8443)
使用WireMockRule,我们可以将WireMockConfiguration.options() 作为构造参数。同样的配置步骤也适用于WireMockServer。
WireMockServer wm
= new WireMockServer(options().port(8080).httpsPort(8443));
//or
@Rule
public WireMockRule wireMockRule
= new WireMockRule(options().port(8080).httpsPort(8443
3.一个WireMock的简单例子
让我们从创建一个非常简单的API存根开始,使用任何HTTP客户端来调用它,并验证模拟服务器是否被击中。
- 要存根于模拟的API响应,请使用
WireMock.stubFor()方法。它接受一个MappingBuilder实例,我们可以用它来建立API的映射信息,如URL、请求参数和正文、头信息、授权等。 - 为了测试API,我们可以使用任何HTTP客户端,如*HttpClient、RestTemplate或TestRestTemplate。在这篇文章中我们将使用TestRestTemplate*。
- 为了验证请求是否击中了模拟API,我们可以使用*WireMock.verify()*方法。
下面是一个非常简单的模拟API的三个步骤的例子。这应该能够帮助我们理解wiremock的基本用法。
如果我们喜欢在测试中使用 BDD语言,那么我们可以用givenThat() 替换stubFor()。
@WireMockTest
public class WireMockTestAnnotationTest {
@Test
void simpleStubTesting(WireMockRuntimeInfo wmRuntimeInfo) {
String responseBody = "Hello World !!";
String apiUrl = "/api-url";
//Define stub
stubFor(get(apiUrl).willReturn(ok(responseBody)));
//Hit API and check response
String apiResponse = getContent(wmRuntimeInfo.getHttpBaseUrl() + apiUrl);
assertEquals(apiResponse, responseBody);
//Verify API is hit
verify(getRequestedFor(urlEqualTo(apiUrl)));
}
private String getContent(String url) {
TestRestTemplate testRestTemplate = new TestRestTemplate();
return testRestTemplate.getForObject(url, String.class);
}
}
4.高级使用方法
4.1.配置API请求
Wiremock提供了很多有用的静态方法来存根API请求和响应部分。
使用get(), put(), post(), delete()和其他方法来匹配相应的HTTP方法。使用*any()*来匹配任何与URL匹配的HTTP方法。
stubFor(delete("/url").willReturn(ok()));stubFor(post("/url").willReturn(ok()));stubFor(any("/url").willReturn(ok()));
使用其他方法,如withHeader (),withCookie(),withQueryParam(),*withRequestBody()*等来设置请求的其他部分。
stubFor(get(urlPathEqualTo("/api-url"))
.withHeader("Accept", containing("xml"))
.withCookie("JSESSIONID", matching(".*"))
.withQueryParam("param-name", equalTo("param-value"))
.withBasicAuth("username", "plain-password")
//.withRequestBody(equalToXml("part-of-request-body"))
.withRequestBody(matchingXPath("//root-tag"))
/*.withMultipartRequestBody(
aMultipart()
.withName("preview-image")
.withHeader("Content-Type", containing("image"))
.withBody(equalToJson("{}"))
)*/
.willReturn(aResponse()));
4.2.配置API响应
一般来说,我们只对响应状态、响应头文件和响应体感兴趣。WireMock支持用简单的方法在响应中存根所有这些组件。
stubFor(get(urlEqualTo("/api-url"))
.willReturn(aResponse()
.withStatus(200)
.withStatusMessage("Everything was just fine!")
.withHeader("Content-Type", "application/json")
.withBody("{ \"message\": \"Hello world!\" }")));
4.3.测试API延迟和超时
为了测试一个延迟的API响应以及当前API如何处理超时,我们可以使用以下方法。
*withFixedDelay()*可以用来配置一个固定的延迟,在指定的毫秒数之后才会返回响应。
stubFor(get(urlEqualTo("/api-url"))
.willReturn(ok().withFixedDelay(2000)));
withRandomDelay()可以用来从一个随机分布中获取延迟。WireMock支持两种类型的随机分布:均匀分布和对数正态分布。
stubFor(get(urlEqualTo("/api-url"))
.willReturn(
aResponse()
.withStatus(200)
.withFixedDelay(2000)
//.withLogNormalRandomDelay(90, 0.1)
//.withRandomDelay(new UniformDistribution(15, 25))
));
我们还可以使用withChunkedDribbleDelay()来模拟一个慢速网络,在这个网络中,响应是分块接收的,中间有时间延迟。它需要两个参数:numberOfChunks和totalDuration。
stubFor(get("/api-url").willReturn(
aResponse()
.withStatus(200)
.withBody("api-response")
.withChunkedDribbleDelay(5, 1000)));
4.4.测试不良响应
在微服务架构中,API随时都可能出现异常行为,所以API消费者必须准备好处理这些情况。Wiremock通过使用*withFault()*方法存根错误的响应来帮助处理这种响应。
stubFor(get(urlEqualTo("/api-url"))
.willReturn(aResponse()
.withFault(Fault.MALFORMED_RESPONSE_CHUNK)));
它支持以下的枚举常量。
- EMPTY_RESPONSE: 返回一个完全空的响应。
- RANDOM_DATA_THEN_CLOSE:发送垃圾然后关闭连接。
- MALFORMED_RESPONSE_CHUNK: 发送一个OK状态头,然后是垃圾信息,然后关闭连接。
- CONNECTION_RESET_BY_PEER:关闭连接,导致 "Connection reset by peer "错误。
5.验证API点击率
如果我们想验证模拟的API是否被命中以及命中的次数,我们可以通过*WireMock.verify()*方法来实现,方法如下。
verify(exactly(1), postRequestedFor(urlEqualTo(api_url))
.withHeader("Content-Type", "application/JSON"));
有不少方法可以验证命中率,比如lessThan()、lessThanOrExactly()、exactly()、moreThanOrExactly()和moreThan()。
verify(lessThan(5), anyRequestedFor(anyUrl()));
verify(lessThanOrExactly(5), anyRequestedFor(anyUrl()));
verify(exactly(5), anyRequestedFor(anyUrl()));
verify(moreThanOrExactly(5), anyRequestedFor(anyUrl()));
verify(moreThan(5), anyRequestedFor(anyUrl()));
6.总结
这个WireMock教程将帮助你通过模拟外部REST APIs开始进行集成测试。它涵盖了初始化WireMockServer的各种方法,并在需要时启动、停止或重置。
我们学习了配置请求和响应存根的基本和高级选项,匹配API响应并验证API命中。我们还学会了在模拟的API中模拟各种成功、失败和错误情况。
学习愉快!!