Skip to content

API 测试与契约测试 ​

MockMvc 的断言语法太啰嗦,一个接口测试写 20 行,可读性差。REST Assured 提供类 BDD 风格的 API 测试。


1. REST Assured ​

1.1 Maven 依赖 ​

xml
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>

1.2 基本用法 ​

java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserApiTest {

    @LocalServerPort
    private int port;

    @BeforeEach
    void setUp() {
        RestAssured.baseURI = "http://localhost";
        RestAssured.port = port;
    }

    // GET 请求
    @Test
    @DisplayName("GET /api/users/1 - 应该返回用户详情")
    void shouldGetUser() {
        given()
            .contentType(ContentType.JSON)
        .when()
            .get("/api/users/1")
        .then()
            .statusCode(200)
            .body("username", equalTo("alice"))
            .body("email", equalTo("[email protected]"))
            .body("id", notNullValue())
            .header("Content-Type", containsString("application/json"));
    }

    // POST 请求
    @Test
    @DisplayName("POST /api/users - 应该创建用户并返回 201")
    void shouldCreateUser() {
        String requestBody = "{\"username\":\"bob\",\"email\":\"[email protected]\"}";

        given()
            .contentType(ContentType.JSON)
            .body(requestBody)
        .when()
            .post("/api/users")
        .then()
            .statusCode(201)
            .body("username", equalTo("bob"))
            .header("Location", containsString("/api/users/"));
    }

    // 验证错误响应
    @Test
    @DisplayName("GET /api/users/999 - 应该返回 404")
    void shouldReturn404() {
        given()
        .when()
            .get("/api/users/999")
        .then()
            .statusCode(404)
            .body("message", equalTo("用户不存在"))
            .body("timestamp", notNullValue());
    }
}

1.3 JSON Schema 验证 ​

java
@Test
@DisplayName("GET /api/users - 响应应该符合 Schema")
void shouldMatchSchema() {
    given()
    .when()
        .get("/api/users")
    .then()
        .statusCode(200)
        .body(matchesJsonSchemaInClasspath("schemas/user-list-schema.json"));
}

JSON Schema 文件(src/test/resources/schemas/user-list-schema.json):

json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "array",
  "items": {
    "type": "object",
    "required": ["id", "username", "email"],
    "properties": {
      "id": { "type": "number" },
      "username": { "type": "string", "minLength": 1 },
      "email": { "type": "string", "format": "email" }
    }
  }
}

1.4 响应时间与集合验证 ​

java
@Test
@DisplayName("GET /api/users - 响应时间应该小于 2 秒")
void shouldRespondWithin2Seconds() {
    given()
    .when()
        .get("/api/users")
    .then()
        .statusCode(200)
        .time(lessThan(2000L), TimeUnit.MILLISECONDS);
}

@Test
@DisplayName("GET /api/users - 应该返回用户列表")
void shouldReturnUserList() {
    given()
    .when()
        .get("/api/users")
    .then()
        .statusCode(200)
        .body("size()", greaterThan(0))
        .body("username", hasItems("alice", "bob"))
        .body("findAll { it.status == 'ACTIVE' }.size()", greaterThan(0));
}

1.5 REST Assured vs MockMvc 对比 ​

维度REST AssuredMockMvc
风格BDD(given/when/then)链式 API
可读性⭐⭐⭐⭐⭐ 接近自然语言⭐⭐⭐ 较啰嗦
环境需要真实/随机端口Mock Servlet
速度较慢(真实 HTTP)快(无网络)
适用API 集成测试Controller 单元测试
Schema 验证✅ 原生支持❌ 需手动
响应时间验证✅ 原生支持❌ 不支持

经验法则:Controller 逻辑测试用 MockMvc(快),API 完整性测试用 REST Assured(可读性好、能测真实 HTTP)。


2. Spring Cloud Contract(概览) ​

2.1 核心思想 ​

微服务 A 修改了接口返回值,微服务 B 不知道,上线后才发现接口不兼容。消费者驱动契约测试(CDC)解决这个问题。

消费者定义期望的接口格式(契约),提供者验证自己满足所有消费者的契约。

groovy
// 消费者侧:定义契约(src/test/resources/contracts/shouldReturnUser.groovy)
Contract.make {
    description "should return user by id"
    request {
        method GET()
        url "/api/users/1"
        headers {
            contentType(applicationJson())
        }
    }
    response {
        status 200
        body([
            id: 1,
            username: "alice",
            email: "[email protected]"
        ])
        headers {
            contentType(applicationJson())
        }
    }
}

2.2 工作流 ​

txt
┌─────────────┐    定义契约     ┌──────────────────┐
│   消费者      │──────────────►│  契约存入仓库      │
│  (Consumer)  │               │ (Git/Maven Repo) │
└─────────────┘               └────────┬─────────┘
                                       │
                                       ▼
┌─────────────┐    验证契约     ┌──────────────────┐
│   提供者      │◄──────────────│  自动生成 Stub 测试 │
│  (Provider)  │               │                  │
└─────────────┘               └──────────────────┘

2.3 Spring Cloud Contract vs 传统集成测试 ​

特性Spring Cloud Contract传统集成测试
契约定义消费者定义(Groovy/YAML)无明确契约
测试方向消费者驱动提供者自测
接口兼容性✅ 自动验证❌ 手动保证
微服务适配✅ 原生支持需要完整环境
学习成本中高低
适用场景多团队微服务协作单体/小团队

经验法则:3 个以上微服务互相调用时引入 Spring Cloud Contract。小团队直接写集成测试更实际。CDC 的核心价值不是技术,而是迫使团队在开发前对齐接口约定。


本章总结 ​

测试类型工具/注解速度适用场景
单元测试JUnit 5 + Mockito⚡ 极快业务逻辑验证
集成测试@SpringBootTest🐢 慢多组件协作
Web 测试@WebMvcTest + MockMvc⚡ 快Controller 层
数据库测试@DataJpaTest / Testcontainers⚡/🐢Repository 层
API 测试REST Assured🐢 中等接口完整性
契约测试Spring Cloud Contract🐢 中等微服务兼容性

一句话总结:单元测试保逻辑,切片测试提速度,集成测试验协作,Testcontainers 兜底真实性——分层测试,各司其职。

Released under the MIT License.