8.5.1 使用模拟 Web 服务器测试 REST 客户端

当应用程序依赖于下游服务时,你应该针对后者的 API 规范测试交互。在本节中,你将首先使用模拟 Web 服务器(充当 Catalog Service)测试 BookClient 类,以确保客户端的正确性。然后你将使用 @DataR2dbcTest 注解和 Testcontainers 通过切片测试测试数据持久化层,就像你在第 5 章中使用 @DataJdbcTest 所做的那样。最后,你将使用 @WebFluxTest 注解为 Web 层编写切片测试,它的工作方式与 @WebMvcTest 相同,但用于响应式应用程序。

你已经拥有了 Spring Boot 测试库和 Testcontainers 的必要依赖。缺少的是对 com.squareup.okhttp3:mockwebserver 的依赖,它将提供运行模拟 Web 服务器的工具。打开 Order Service 项目的 build.gradle 文件,添加缺少的依赖。

代码清单 8.27 添加 OkHttp MockWebServer 的测试依赖

dependencies {
 // ... 其他依赖
 testImplementation 'com.squareup.okhttp3:mockwebserver'
}

让我们从测试 BookClient 类开始。

使用模拟 Web 服务器测试 REST 客户端

OkHttp 项目提供了一个模拟 Web 服务器,你可以使用它来测试与下游服务的基于 HTTP 的请求/响应交互。BookClient 返回一个 Mono 对象,因此你可以使用 Project Reactor 提供的方便工具来测试响应式应用程序。StepVerifier 对象允许你通过流畅的 API 按步骤处理响应式流并编写断言。

首先,让我们设置模拟 Web 服务器并在新的 BookClientTests 类中配置 WebClient 使用它。

代码清单 8.28 使用模拟 Web 服务器准备测试设置

package com.polarbookshop.orderservice.book;

import java.io.IOException;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.*;
import org.springframework.web.reactive.function.client.WebClient;

class BookClientTests {
 private MockWebServer mockWebServer;
 private BookClient bookClient;

 @BeforeEach
 void setup() throws IOException {
 this.mockWebServer = new MockWebServer();
 this.mockWebServer.start(); // 在运行测试用例之前启动模拟服务器

 var webClient = WebClient.builder()
 .baseUrl(mockWebServer.url("/").uri().toString()) // 使用模拟服务器 URL 作为 WebClient 的基础 URL
 .build();
 this.bookClient = new BookClient(webClient);
 }

 @AfterEach
 void clean() throws IOException {
 this.mockWebServer.shutdown(); // 完成测试用例后关闭模拟服务器
 }
}

接下来,在 BookClientTests 类中,你可以定义一些测试用例来验证 Order Service 中客户端的功能。

代码清单 8.29 测试与 Catalog Service 应用程序的交互

package com.polarbookshop.orderservice.book;

// ... 其他导入
import okhttp3.mockwebserver.MockResponse;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

class BookClientTests {
 private MockWebServer mockWebServer;
 private BookClient bookClient;

 // ... setup 和 clean 方法

 @Test
 void whenBookExistsThenReturnBook() {
 var bookIsbn = "1234567890";
 var mockResponse = new MockResponse() // 定义模拟服务器要返回的响应
 .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
 .setBody("""
 {
 "isbn": %s,
 "title": "Title",
 "author": "Author",
 "price": 9.90,
 "publisher": "Polarsophia"
 }
 """.formatted(bookIsbn));
 mockWebServer.enqueue(mockResponse); // 将模拟响应添加到模拟服务器处理的队列中

 Mono<Book> book = bookClient.getBookByIsbn(bookIsbn);

 StepVerifier.create(book) // 使用 BookClient 返回的对象初始化 StepVerifier 对象
 .expectNextMatches(b -> b.isbn().equals(bookIsbn)) // 断言返回的 Book 具有请求的 ISBN
 .verifyComplete(); // 验证响应式流已成功完成
 }
}

让我们运行测试并确保它们成功。打开终端窗口,导航到 Order Service 项目的根文件夹,运行以下命令:

$ ./gradlew test --tests BookClientTests

注意: 使用模拟时,可能会出现测试结果取决于测试用例执行顺序的情况,这在相同操作系统上往往相同。为了防止不需要的执行依赖,你可以使用 @TestMethodOrder(MethodOrderer.Random.class) 注解测试类,以确保每次执行时使用伪随机顺序。

测试完 REST 客户端部分后,你可以继续验证 Order Service 的数据持久化层。

results matching ""

    No results matching ""