接口测试利器:REST Assured + WireMock

系列专栏:测试工程师每日一博 · Day 5

日期:2026-07-11

关键词:REST Assured、WireMock、接口测试、HTTP 桩、契约测试入门

阅读对象:写完 JUnit / Mockito 单元测试、想往"接口层"上探一层但又不想上 E2E 的工程师 / 测试


开篇:接口测试这块"夹心层"

回头看一下我们已经走过的金字塔,你会看到一个明显的空白:

  • 底层(单元测试):Day 1-3,JUnit + Mockito,毫秒级,把内部分支测得清清楚楚
  • 中间一层(Testcontainers):Day 4,真实数据库,验持久层
  • 再往上一层?

这块空白叫"接口层 "。它的核心问题是:我的服务暴露了一个 HTTP API,我怎么测它------又怎么测它依赖的别人的 HTTP API?

拿最常见的电商场景说:你写了个订单查询接口 GET /orders/{id},返回订单详情。这个接口内部逻辑没问题(单元测试覆盖了),数据库行为也对(Testcontainers 覆盖了),但订单接口会调一个外部的"库存服务" GET /inventory/{sku} 才能拼出完整的库存数量。

测这个"接口 + 它依赖的另一个接口",你立刻撞上两个难题:

  1. 怎么发请求、断响应 ------不能只会 curl
  2. 怎么隔离外部库存服务------你总不能测一次订单接口,就发动一次真实库存查询

这两件事,Java 生态有两位选手是事实标准:REST Assured 负责"发请求、断响应",WireMock 负责"假装一个外部服务"。它俩组合,就是接口层测试的金组合。今天我们就把这两件武器一次讲透。


一、为什么需要两个工具,而不是一个

新手最容易栽的坑:只用 REST Assured,不用 WireMock,结果所有接口测试都依赖真实的下游服务。一旦库存服务挂了、改了字段、改变了返回结构,你的接口测试就 flaky------而问题根本不在你代码里。

反过来:只用 WireMock,不用 REST Assured ,你得手写一堆 HttpClient、手动拼 JSON 断言,代码难看到没法维护。

所以两个工具的分工是这样的:

工具 干什么 类比
REST Assured 给我测的服务发请求、断响应 我扮演"客户端"的角色
WireMock 假装一个外部依赖,我设定它返回什么 我扮演"下游服务"的角色
复制代码
   客户端 ──> [REST Assured] ──> 被测服务 ──> [WireMock 假装的库存服务]
   (我)        (发请求+断响应)    (订单API)      (我设定的桩返回)

一句话总结:REST Assured 是入站方向、WireMock 是出站方向。合起来才能把一个接口的"全链路"测干净。


二、REST Assured:把 HTTP 测试写得像散文

2.1 入门:一段就懂

java 复制代码
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

given()
    .header("Accept", "application/json")
.when()
    .get("/orders/1001")
.then()
    .statusCode(200)
    .body("orderId", equalTo(1001))
    .body("status", equalTo("PAID"));

这段代码做了三件事:given() 准备请求 → when() 真正发 → then() 断响应。注意全程链式 BDD 风格,读起来像一句话。这种写法是 REST Assured 的灵魂。

2.2 对比:为什么不要直接用 HttpClient

同样的断言,如果用裸 HttpClient + JUnit 写,会长这样:

java 复制代码
// ❌ 又长又难看
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create("http://localhost:8080/orders/1001"))
        .header("Accept", "application/json")
        .GET().build();
HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());

assertEquals(200, resp.statusCode());
// 解析 JSON 还要引一个 Jackson / Gson,然后手写 JsonPath
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(resp.body());
assertEquals(1001, node.get("orderId").asInt());
assertEquals("PAID", node.get("status").asText());

足足长一倍,而且 JSON 断言特别折磨人。REST Assured 把这些全包了------它的存在意义就是消除 HTTP 测试的样板代码

2.3 REST Assured 的核心三段式

java 复制代码
given().                                // ← 请求前置(头、cookie、参数、body)
    header(...).
    queryParam(...).
    body(someObject).                   // 自动序列化 POJO 为 JSON
when().
    post("/orders").                    // ← 真正发请求
then().
    statusCode(201).
    header("Location", containsString("/orders/")).
    body("id", notNullValue()).
    body("createdAt", matchesPattern("\\d{4}-\\d{2}-\\d{2}.*")).
    extract().path("id");               // ← 从响应中提取值

记住一个口诀:given-when-then。中间的几个常用方法:

阶段 常用方法 用途
given() .header(), .contentType(JSON), .body(pojo) 准备
when() .get(), .post(), .put(), .delete() 动作
then() .statusCode(), .body(path, matcher), .extract() 断言 + 提取

最强大的是 .body(path, matcher)------pathJsonPath 表达式 (如 "items[0].sku"),matcher 是 Hamcrest 匹配器。这是 REST Assured 比裸 HttpClient 高一个数量级的核心原因。

2.4 配合 Spring Boot:@SpringBootTest + MockMvc

实际工程里,我们常需要"测一个 Spring Controller,但不真启 HTTP 端口"。Spring 提供了 MockMvc,REST Assured 原生支持它:

java 复制代码
@SpringBootTest
@AutoConfigureMockMvc
class OrderControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    void 按id查询_存在订单_应返回详情() {
        // 把 RestAssured 注册到 MockMvc 上
        RestAssuredMockMvc.mockMvc(mockMvc);

        given()
            .accept(ContentType.JSON)
        .when()
            .get("/orders/1001")
        .then()
            .statusCode(200)
            .body("orderId", equalTo(1001));
    }
}

这种用法的好处:不需要真起 HTTP 端口,毫秒级启动,又享受了 REST Assured 的优雅语法。属于"接口测试"和"切片测试"的中间地带,工程里非常常用。


三、WireMock:假装一个外部服务

3.1 一句话定位

WireMock 是一个嵌入式 HTTP 桩服务器,你在测试里指定它收到什么请求时返回什么响应,然后让你的代码去调用它,从而测"代码如何处理下游的各种返回"。

它和 Mockito 的 when(mock.x()).thenReturn(y) 概念完全一样,只是粒度从"Java 方法"提到了"HTTP 接口"。

3.2 最小例子:30 秒看懂

java 复制代码
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;

@RegisterExtension
static WireMockExtension wm = WireMockExtension.newInstance()
        .options(wireMockConfig().dynamicPort())
        .build();

@Test
void 应当能桩一个简单GET() {
    // 1. 定义桩
    wm.stubFor(get(urlEqualTo("/inventory/SKU-001"))
            .willReturn(aResponse()
                    .withStatus(200)
                    .withHeader("Content-Type", "application/json")
                    .withBody("{\"sku\":\"SKU-001\",\"qty\":42}")));

    // 2. 用任意 HTTP 客户端去打
    int qty = HttpUtil.get(wm.getRuntimeInfo().getHttpBaseUrl() + "/inventory/SKU-001")
                     .getInt("qty");

    assertEquals(42, qty);
}

WireMock 在 dynamicPort() 上启动,你拿到一个随机的本地端口 ------和 Testcontainers 一样的隔离逻辑。stubFor 定义桩,HTTP 请求来时返回你 willReturn 的内容。

3.3 WireMock 的两大利器:Stubbing + Verification

跟 Mockito 一样,WireMock 也有"设定返回 "和"验证调用"两种模式:

java 复制代码
// Stubbing: 当这样请求时,返回那样
wm.stubFor(post(urlPathEqualTo("/inventory/decrement"))
        .withRequestBody(matchingJsonPath("$.sku", equalTo("SKU-001")))
        .willReturn(ok("{\"success\":true}")));

// Verification: 验证请求确实来了、参数对
wm.verify(postRequestedFor(urlPathEqualTo("/inventory/decrement"))
        .withRequestBody(matchingJsonPath("$.qty", equalTo("3"))));

wm.verify(3, postRequestedFor(urlPathEqualTo("/inventory/decrement")));  // 调了 3 次

这是 WireMock 比"简单的 Mock Server"强的地方:你能验证下游到底被打了几次、参数是不是真的对。这对"我的代码该不该调下游、调几次"这种问题极其有用。

3.4 处理"麻烦场景":延迟、超时、错误码

接口测试不只是测"成功路径",更要测下游故障下你的代码怎么表现。WireMock 对这些场景几乎是免费的:

java 复制代码
// 下游永远返回 500
wm.stubFor(get("/inventory/SKU-001").willReturn(aResponse().withStatus(500)));

// 下游返回 200 但延迟 5 秒(测超时重试)
wm.stubFor(get("/inventory/SKU-001")
        .willReturn(ok("{}").withFixedDelay(5000)));

// 下游返回畸形 JSON
wm.stubFor(get("/inventory/SKU-001")
        .willReturn(aResponse().withBody("not a json").withStatus(200)));

// 下游前 2 次失败,第 3 次成功(测重试逻辑)
wm.stubFor(get("/inventory/SKU-001")
        .inScenario("retry")
        .willReturn(aResponse().withStatus(503))
        .willSetStateTo("SECOND_TRY"));

这是 WireMock 对接口测试最硬核的价值------你怎么能在真实库存服务里制造"延迟 5 秒"、"前两次失败"这种场景?用 WireMock 一行配置就搞定。这是单元测试永远测不出来的能力。


四、实战:测一个订单查询接口

把两个工具串起来。场景:

被测:OrderController 暴露 GET /orders/{id}

它内部调下游库存服务 GET /inventory/{sku} 获取每个商品的库存。

我们要测:

复制代码
1. 库存服务正常时,订单接口返回的明细里库存数量对得上
2. 库存服务超时时,订单接口要有降级(比如把库存标成 `unknown`)
3. 订单接口要正确地只对**存在商品的 SKU** 调库存服务,不打无效请求

4.1 被测:订单 Controller + Service

java 复制代码
// OrderController.java
@RestController
@RequestMapping("/orders")
public class OrderController {

    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/{id}")
    public OrderDetail getById(@PathVariable long id) {
        return orderService.loadDetail(id);
    }
}

// OrderService.java ------ 业务编排
public class OrderService {

    private final OrderRepository orderRepo;       // 自己的库存
    private final InventoryClient inventoryClient; // 远程库存服务

    public OrderService(OrderRepository orderRepo, InventoryClient inventoryClient) {
        this.orderRepo = orderRepo;
        this.inventoryClient = inventoryClient;
    }

    public OrderDetail loadDetail(long orderId) {
        Order order = orderRepo.findById(orderId)
                .orElseThrow(() -> new OrderNotFoundException(orderId));

        List<ItemView> items = order.getItems().stream()
                .map(it -> {
                    int qty;
                    try {
                        qty = inventoryClient.getQuantity(it.getSku());
                    } catch (Exception e) {
                        qty = -1;   // 降级:库存未知
                    }
                    return new ItemView(it.getSku(), it.getName(), qty);
                })
                .toList();

        return new OrderDetail(order.getId(), order.getStatus(), items);
    }
}

// InventoryClient.java ------ 调用下游库存的 HTTP 客户端(简化)
public class InventoryClient {

    private final String baseUrl;
    private final HttpClient http = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(1)).build();

    public InventoryClient(String baseUrl) { this.baseUrl = baseUrl; }

    public int getQuantity(String sku) {
        try {
            HttpResponse<String> resp = http.send(
                    HttpRequest.newBuilder(URI.create(baseUrl + "/inventory/" + sku))
                            .timeout(Duration.ofSeconds(2)).GET().build(),
                    HttpResponse.BodyHandlers.ofString());
            if (resp.statusCode() != 200) throw new RuntimeException("inv " + resp.statusCode());
            return new ObjectMapper().readTree(resp.body()).get("qty").asInt();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

注意 OrderService.loadDetail 的 try/catch------这是降级 :库存查不到时,把数量标记为 -1,接口仍返回 200。这种"非功能属性降级"在单元测试里测不出来,因为单元测试用 Mockito 模拟 InventoryClient 时,客户端内部的超时行为被跳过了。接口测试才能真正测它

4.2 完整接口测试

java 复制代码
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.web.context.WebApplicationContext;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
import static org.hamcrest.Matchers.*;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class OrderControllerIT {

    @RegisterExtension
    static WireMockExtension inventoryService = WireMockExtension.newInstance()
            .options(wireMockConfig().dynamicPort())
            .build();

    @DynamicPropertySource
    static void 把库存服务地址指向 WireMock(DynamicPropertyRegistry registry) {
        // 关键:让 Spring 真的把 InventoryClient.baseUrl 注入成 WireMock 地址
        registry.add("inventory.base-url", inventoryService::baseUrl);
    }

    @BeforeEach
    void setUp(@Autowired WebApplicationContext ctx) {
        RestAssuredMockMvc.webAppContextSetup(ctx);
        inventoryService.resetAll();   // 每个用例前清掉旧桩
    }

    // --------- 1. 快乐路径:库存服务正常,返回的明细里有真实库存量 ---------
    @Test
    @DisplayName("订单 + 库存全正常时,接口返回每个 SKU 的库存量")
    void shouldReturnInventoryQuantities_whenHappyPath() {
        // 假数据:本地订单
        orderRepo.save(new Order(1001L, "PAID",
                List.of(new Item("SKU-001", "Java 编程之道"), new Item("SKU-002", "测试金字塔"))));
        // WireMock 桩:每个 SKU 的库存量
        inventoryService.stubFor(get(urlEqualTo("/inventory/SKU-001"))
                .willReturn(okJson("{\"sku\":\"SKU-001\",\"qty\":42}")));
        inventoryService.stubFor(get(urlEqualTo("/inventory/SKU-002"))
                .willReturn(okJson("{\"sku\":\"SKU-002\",\"qty\":7}")));

        given()
            .accept("application/json")
        .when()
            .get("/orders/1001")
        .then()
            .statusCode(200)
            .body("orderId", equalTo(1001))
            .body("status", equalTo("PAID"))
            .body("items", hasSize(2))
            .body("items[0].sku", equalTo("SKU-001"))
            .body("items[0].quantity", equalTo(42))
            .body("items[1].quantity", equalTo(7));
    }

    // --------- 2. 降级路径:库存超时,接口返回的库存应为 -1(降级标记)---------
    @Test
    @DisplayName("库存服务超时时,接口应当降级为 quantity=-1 且仍返回 200")
    void shouldFallback_whenInventoryTimesOut() {
        orderRepo.save(new Order(1002L, "PAID",
                List.of(new Item("SKU-001", "Java 编程之道"))));
        // 桩:库存服务延迟 5 秒(实际 HTTP 客户端 2 秒超时会抛异常)
        inventoryService.stubFor(get(urlEqualTo("/inventory/SKU-001"))
                .willReturn(okJson("{\"sku\":\"SKU-001\",\"qty\":42}")
                        .withFixedDelay(5000)));

        given()
            .accept("application/json")
        .when()
            .get("/orders/1002")
        .then()
            .statusCode(200)
            .body("items[0].quantity", equalTo(-1));   // 降级标记
    }

    // --------- 3. 验证出站调用 ---------
    @Test
    @DisplayName("对存在的 SKU,精确调用库存服务一次;不存在的 SKU 不应打库存")
    void shouldOnlyCallInventoryForExistingSkus() {
        orderRepo.save(new Order(1003L, "PAID",
                List.of(new Item("SKU-001", "书1"), new Item("SKU-002", "书2"))));
        inventoryService.stubFor(get(urlMatching("/inventory/.*"))
                .willReturn(okJson("{\"qty\":1}")));

        given().accept("application/json").when().get("/orders/1003");

        // 验证下游被打了 2 次(每个 SKU 一次)
        inventoryService.verify(2, getRequestedFor(urlMatching("/inventory/.*")));
        inventoryService.verify(getRequestedFor(urlEqualTo("/inventory/SKU-001")));
        inventoryService.verify(getRequestedFor(urlEqualTo("/inventory/SKU-002")));
    }

    // --------- 4. 边界:订单不存在 ---------
    @Test
    @DisplayName("不存在的订单 → 404")
    void shouldReturn404_whenOrderMissing() {
        given()
            .accept("application/json")
        .when()
            .get("/orders/9999")
        .then()
            .statusCode(404);
    }
}

4.3 这套测试示范了什么

测试场景 用的工具 独特价值
正常拼出订单 + 库存明细 REST Assured 的 body(path, matcher) + WireMock stubFor 验证编排正确
下游超时降级 WireMock withFixedDelay(5000) + REST Assured 断言 quantity=-1 单元测试测不出的非功能属性
出站请求验证 WireMock verify() 验证"代码有没有打无效请求"
错误响应路径 REST Assured statusCode(404) 接口的失败路径

最有价值的是第 2 用例 :它在毫秒级测试里实现了"下游慢 5 秒、客户端应当降级"这个真实网络场景。这件事用 Mockito 怎么也做不出------因为 Mockito 跳过了 HTTP 客户端内部,你只能 mock"客户端抛异常",模仿不出"打开连接但等不回来"的语义。这就是接口层测试存在的根本意义。

4.4 注意这里 webEnvironment = MOCK 的选择

我用 SpringBootTest.WebEnvironment.MOCK + MockMvc,而不是 RANDOM_PORT。两者差异:

模式 真实 HTTP 端口 启动速度 适用
MOCK (MockMvc) ❌ 不开 大多数接口测试(不需要测过滤器 / 序列化的极底层细节)
RANDOM_PORT ✅ 开真端口 需要测真 HTTP 行为(超时、连接级别语义)

经验:优先用 MOCK,只有当你怀疑"MockMvc 上能跑、真实 HTTP 上不一定能跑"时,再用 RANDOM_PORT。今天的第 2 个用例(超时降级)其实更对症 RANDOM_PORT------但 MOCK + WireMock 也能验出来,因为客户端的 HTTP 客户端是真的(WireMock 也真在跑,真超时)。


五、WireMock 进阶:三个值得掌握的特性

5.1 请求匹配(withRequestBody / withQueryParam)

WireMock 的桩不是只能按 URL 匹配,还可以按 body / header / query param 任意组合:

java 复制代码
wm.stubFor(post(urlPathEqualTo("/inventory/decrement"))
        .withRequestBody(matchingJsonPath("$.sku", equalTo("SKU-001")))
        .withHeader("Authorization", containing("Bearer"))
        .willReturn(ok()));

matchingJsonPath 让你用 JsonPath 精确匹配请求 body 的字段------比纯字符串匹配健壮得多。

5.2 场景(Scenario):模拟状态机

java 复制代码
// 重试场景:前 2 次失败,第 3 次成功
wm.stubFor(get("/inventory/SKU-001")
        .inScenario("retry")
        .whenScenarioStateIs("STARTED")
        .willReturn(aResponse().withStatus(503))
        .willSetStateTo("SECOND_TRY"));

wm.stubFor(get("/inventory/SKU-001")
        .inScenario("retry")
        .whenScenarioStateIs("SECOND_TRY")
        .willReturn(aResponse().withStatus(503))
        .willSetStateTo("THIRD_TRY"));

wm.stubFor(get("/inventory/SKU-001")
        .inScenario("retry")
        .whenScenarioStateIs("THIRD_TRY")
        .willReturn(okJson("{\"qty\":42}")));

模拟"前几次失败、最后一次成功",这是测重试逻辑的利器。

5.3 录制(Recording)------把真实响应变成桩

WireMock 有一个被忽略的强大功能:录制。你让它代理一次真实下游服务,它会把所有响应自动录下来,后续测试就用这些录下来的响应做桩。这对"下游有 50 个接口、手写桩写到崩溃"的场景极有帮助。

java 复制代码
// 启动 WireMock 并让它代理库存服务的真实地址
wm.startRecording("http://real-inventory-service.internal");

// 这期间执行一次真实调用...

List<StubMapping> recorded = wm.stopRecording().getStubMappings();
// recorded 可以保存、编辑、纳入版本控制

录下来的桩可以转成 JSON 文件、纳入版本控制,变成"契约快照"。这就引出本系列未来的主题------契约测试(Day N 会专门写 Pact)。


六、三个常见误区

误区 1:桩写得过于具体,一改字段就崩

java 复制代码
// ❌ 桩的响应写死所有字段,等于把契约焊死
wm.stubFor(get("/inventory/SKU-001")
        .willReturn(okJson("{\"sku\":\"SKU-001\",\"qty\":42,\"warehouse\":\"WH-3\",\"updatedAt\":\"2026-07-12T08:00:00Z\",\"owner\":\"张三\"}")));

下游改一个无关字段(比如 owner 改名),你的桩就不像真实服务了------但你测试还过,因为 REST Assured 只断 quantity 字段。

对策 :桩只写你测试关心的字段 。其他字段用 null 或省略。桩不是文档,是测试道具

误区 2:用 WireMock 测自己的服务(应该用 REST Assured)

新手有时分不清方向,把 WireMock 桩挂在自己的接口前面、用 HttpClient 打它------这是把方向搞反了。WireMock 用于"外部依赖",REST Assured 用于"测自己"

误区 3:不 reset 桩,用例间相互串扰

java 复制代码
@BeforeEach
void setUp() {
    wm.resetAll();   // 必须!
}

不加这行,前一个用例的桩会留下来,后一个用例可能莫名其妙地匹配到旧桩、返回错数据。这是 WireMock 用例 flaky 的头号原因


七、把 REST Assured + WireMock 接回金字塔

今天我们继续在**中间层(集成测试)**里深耕。Day 4 的 Testcontainers 解决了"持久层粘连真实数据库",今天这两个工具解决了"接口层粘连真实下游服务"。中间层至此基本完整了:

复制代码
            E2E (10%)              ← 还没进
         ───────────────
        接口 / 集成 (20%)
            ├── Testcontainers (Day 4)  ← 数据库
            ├── REST Assured (Day 5)    ← 测我对外暴露的接口
            └── WireMock (Day 5)        ← 隔离我依赖的下游
        ───────────────
        单元 (70%)               ← Day 1-3

至此,你已经在中间层有了一把完整的工具:数据库 + 自己接口 + 下游接口。绝大多数 Java 后端的集成测试需求,这把组合都能覆盖。

明天的主题从"横向加工具"切到"纵向看体系"------性能测试初探。我们会从金字塔跳出来,讨论"压力测试 / 负载测试 / 容量测试"的区别,以及 JMeter 和 Locust 在实战中各自的用法。这是金字塔之外的另一根支柱。


八、思考题(留给明天的引子)

Q1 :我们用 withFixedDelay(5000) 模拟下游慢。但真实世界的"慢"是网络抖动------有的请求 200ms,有的 2s,偶尔 30s。WireMock 怎么模拟这种分布式的延迟?

提示:withLogNormalRandomDelay()withUniformRandomDelay(),WireMock 内置概率分布。
Q2 :今天的接口测试里,我只测了"超时"和"成功"两种下游响应。但生产环境的故障模式远不止于此 :连接拒绝、DNS 失败、TLS 证书错乱。MockMvc 模式能测这些吗?

(提示:不能,因为 MockMvc 根本没真起 HTTP。需要 RANDOM_PORT + WireMock 才能模拟"连接级别"的故障。这是 Day 7 CI 分层里会细谈的话题------但这种"故障注入"测试该不该上 CI、怎么权衡时间成本,是非常值得思考的问题。)


九、总结(TL;DR)

  • 🎯 两个工具,一个方向:REST Assured 是入站(测我暴露的接口)、WireMock 是出站(假装我依赖的服务)。
  • 📝 REST Assured 灵魂三段式 :given().when().then(),配合 body(path, matcher) 把 JSON 断言写得像散文。
  • 🪝 WireMock 两大利器:stubbing(设定返回)+ verification(验证调用次数和参数)。
  • 💥 WireMock 最强价值 :模拟超时、错误码、重试、网络异常------这是单元测试永远做不到的
  • 🔗 黄金组合:Testcontainers 处理数据库、WireMock 处理下游、REST Assured 处理自己的接口------中间层三件套集齐。
  • ⚠️ 三个误区:桩写得过具体、方向搞反、不 reset 桩导致 flaky。

明天 Day 6:《性能测试初探:JMeter / Locust,从压测计划到瓶颈定位》------我们离开金字塔的"功能正确性"维度,跳到"非功能"维度。功能测得再对、压一下崩了,就白测。如果这篇对你有帮助,欢迎点赞收藏 ⭐。下篇见。

相关推荐
tyqtyq223 小时前
营养餐单规划:AI 科学饮食规划系统的鸿蒙实现
人工智能·学习·华为·职场和发展·生活·harmonyos
鱼听禅3 小时前
Ubuntu学习笔记-安装docker容器
学习·ubuntu·docker
Loving_enjoy4 小时前
DFT可测试性设计规则与检查:确保芯片可测试性的关键步骤
学习
r_oo_ki_e_5 小时前
Java反射机制学习笔记
java·笔记·学习
绝世番茄6 小时前
鸿蒙HarmonyOS ArkTS原生学习:缩放手势实现 PinchToZoom 深度解析
学习·华为·harmonyos·鸿蒙
a1117766 小时前
SLAM 学习笔记(四)后端(KF BA)
笔记·学习
XGeFei7 小时前
【Django学习笔记】—— Django 高并发通俗讲解
笔记·学习·django
六点_dn7 小时前
Linux学习笔记-chmod命令
linux·笔记·学习
MartinYeung57 小时前
[论文学习]揭示大语言模型智能体记忆模块中的隐私风险
人工智能·学习·语言模型