WireMock - 使用模拟API进行集成测试的实例教程

1,475 阅读6分钟

微服务架构允许我们独立开发、测试和部署一个应用程序的不同组件。虽然这样的组件可以独立开发,但孤立地进行测试是具有挑战性的。对于微服务的真正集成测试,我们必须测试它与其他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客户端,如*HttpClientRestTemplateTestRestTemplate。在这篇文章中我们将使用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()模拟一个慢速网络,在这个网络中,响应是分块接收的,中间有时间延迟。它需要两个参数:numberOfChunkstotalDuration

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中模拟各种成功、失败和错误情况。

学习愉快!!

Github上的Sourceocde