用MockWebServer和JUnit模拟API的方法

829 阅读4分钟

MockWebServer是一个有用的库,用于模拟当前组件(被测试)所依赖的API。这样的模拟API在微服务架构中是非常有用的,因为我们要同时开发多个依赖性服务。

在本教程中,我们将学习如何在JUnit 5测试中设置MockWebServer。我们将使用SpringWebClient作为HTTP客户端来调用模拟的API。

1.Maven的依赖性

MockWebServerokhttp3库的一部分,所以我们需要导入以下两个依赖项。

<dependency>
   <groupId>com.squareup.okhttp3</groupId>
   <artifactId>okhttp</artifactId>
   <version>4.10.0</version>
   <scope>test</scope>
</dependency>

<dependency>
   <groupId>com.squareup.okhttp3</groupId>
   <artifactId>mockwebserver</artifactId>
   <version>4.10.0</version>
   <scope>test</scope>
</dependency>

2.启动和停止MockWebServer

我们可以使用MockWebServer,就像其他类似的库,如WireMock。通常情况下,我们

  • 配置Mock
  • 在测试开始前启动服务器
  • 运行测试并验证响应
  • 在测试结束后停止服务器

下面的例子使用@BeforeAll@AfterAll 钩子来启动和停止服务器。默认情况下,服务器在8080端口启动。我们可以通过在start() 方法中指定使用不同的端口。

public class MockWebServerTests {
  public static MockWebServer server;

  @BeforeAll
  static void setUp() throws IOException {
    server = new MockWebServer();
    server.start(8080);
  }

  @AfterAll
  static void tearDown() throws IOException {
    server.shutdown();
  }
}

如果我们想的话,我们可以为每个单元测试创建一个新的服务器实例

public void test() throws Exception {
   MockWebServer server = new MockWebServer();

   server.start(8080);

   //---test---

   server.shutdown();
}

3.设置Mocks

3.1.使用Server.enqueue()

为了设置模拟,我们可以使用Server.enqueue() 方法。我们可以根据自己的需要来排队获取响应。然后我们可以点击模拟的API URL来依次获取模拟的响应。

public void test() throws Exception {
  MockWebServer server = new MockWebServer();

  server.enqueue(new MockResponse().setBody("message 1"));
  server.enqueue(new MockResponse().setBody("message 2"));
  server.enqueue(new MockResponse().setBody("message 3"));

  server.start();

  //more code
}

3.2.使用Dispatcher

返回排队的响应并不是在所有情况下都适合。我们可以使用Dispatcher 类来设计我们自己的逻辑来返回API响应

在下面的例子中,我们使用一个调度器来匹配传入请求的API URI,然后返回匹配URI的响应。

public class MockWebServerTests {
  public static MockWebServer server;

  final static Dispatcher dispatcher = new Dispatcher() {

    @Override
    public MockResponse dispatch(RecordedRequest request) throws InterruptedException {

      switch (request.getPath()) {
        case "/api-url-one":
          return new MockResponse()
              .setResponseCode(201);

        case "/api-url-two":
          return new MockResponse()
              .setHeader("x-header-name", "header-value")
              .setResponseCode(200)
              .setBody("<response />");

        case "/api-url-three":
          return new MockResponse()
              .setResponseCode(500)
              .setBodyDelay(5000, TimeUnit.SECONDS)
              .setChunkedBody("<error-response />", 5);

        case "/api-url-four":
          return new MockResponse()
              .setResponseCode(200)
              .setBody("{\"data\":\"\"}")
              .throttleBody(1024, 5, TimeUnit.SECONDS);
      }
      return new MockResponse().setResponseCode(404);
    }
  };

  @BeforeAll
  static void setUp() throws IOException {
    server = new MockWebServer();
    server.setDispatcher(dispatcher);
    server.start(8080);
  }

  //more test code
}

4.编写测试

4.1.正常的JUnit测试

在设置好模拟后,我们可以使用Spring WebClient打出模拟的API。要获得API主机的URL,请使用server.getHostName() 方法。

WebClient webClient = WebClient
        .create(String.format("http://%s:8080", server.getHostName()));

最后,点击模拟的API,并根据需要传递请求参数和主体。

Mono<String> apiResponse = webClient.post()
  .uri("/api-url-two")
  .body(Mono.just("<data />"), String.class)
  .header("Authorization", "Basic " +
      Base64Utils.encodeToString(("username:password").getBytes(UTF_8)))
  .retrieve()
  .bodyToMono(String.class);

一旦API响应可用,我们就可以投射Reactor的StepVerifier 来测试这些异步响应。

StepVerifier.create(apiResponse)
  .expectNext("<response />")
  .verifyComplete();

4.2.错误条件

API响应不会一直成功。我们可能会得到不同的错误代码和其他失败,如网络问题和延迟。MockWebServer支持这些类型的错误模拟响应。

例如,我们可以使用setBodyDelay() 方法测试超时逻辑和延迟响应

new MockResponse()
     .setResponseCode(200)
     .setBodyDelay(5000, TimeUnit.MILLISECONDS)
     .setBody("<data-response />");

为了测试缓慢的网络,我们可以使用setChunkedBody() 方法来分块发送响应。给定的模拟将分5个小块发送响应。

new MockResponse()
     .setResponseCode(200)
     .setChunkedBody("<data-response />", 5);

5.验证服务器统计信息

有时候,验证一个请求在模拟服务器上被击中了多少次是很重要的。这在我们实现和测试重试逻辑时特别有用。我们可以使用RecordedRequest实例来获取HTTP请求MockWebServer的详细信息,以确保我们的WebClient发送正确。

RecordedRequest request = server.takeRequest();

assertEquals("/api-url-two", request.getPath());
assertEquals("POST", request.getMethod());
assertNotNull(request.getHeader("Authorization"));
assertEquals("<data />", request.getBody().readUtf8());

6.结语

在本教程中,我们学习了如何使用MockWebServer来模拟API和响应,然后使用WebClient来消费这些API。

我们学会了启动和停止服务器、设置模拟、编写成功和错误测试、验证发送请求的细节等等。还有一些其他流行的替代品,你可以考虑,如WireMock

学习愉快!!

Github上的源代码