契约测试示例

以下是基于Node.js技术栈的契约测试(Contract Tests)实现示例,以用户加入封闭小组的审批流程为例,使用Pact工具进行消费者驱动契约测试:


场景说明

  • 消费者服务前端应用(通过API Gateway调用Auth Service)
  • 提供者服务Auth Service(处理封闭小组的加入审批)
  • 关键接口POST /approvals(提交加入申请)和 GET /approvals/{id}(查询审批状态)

步骤1:消费者端定义契约(前端测试)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// tests/contract/auth-service.consumer.spec.js
const { Pact } = require('@pact-foundation/pact');
const { ApprovalClient } = require('../clients/approval-client'); // 封装的API客户端

describe('Auth Service Contract', () => {
const provider = new Pact({
consumer: 'WebFrontend',
provider: 'AuthService',
port: 1234,
});

beforeAll(() => provider.setup());
afterEach(() => provider.verify());
afterAll(() => provider.finalize());

describe('Submit Group Join Request', () => {
beforeEach(() => {
// 定义预期的请求和响应
return provider.addInteraction({
state: 'user exists and group is closed',
uponReceiving: 'a request to create approval',
withRequest: {
method: 'POST',
path: '/approvals',
headers: { 'Content-Type': 'application/json' },
body: {
userId: 'user123',
groupId: 'group456'
}
},
willRespondWith: {
status: 201,
headers: { 'Content-Type': 'application/json' },
body: {
id: Matchers.uuid('d1f0a3d4'), // 使用正则匹配器
status: 'PENDING'
}
}
});
});

it('should create a pending approval', async () => {
const client = new ApprovalClient('http://localhost:1234');
const response = await client.submitRequest('user123', 'group456');

expect(response.id).toBeTruthy();
expect(response.status).toEqual('PENDING');
});
});
});

运行测试后会生成契约文件(webfrontend-authservice.json


步骤2:提供者端验证契约(Auth Service测试)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// tests/contract/auth-service.provider.spec.js
const { Verifier } = require('@pact-foundation/pact');
const { startServer, stopServer } = require('../../src/server');

describe('Auth Service Provider Verification', () => {
const port = 3000;
let server;

beforeAll(async () => {
server = await startServer(port); // 启动真实的Auth Service实例
});

afterAll(async () => {
await stopServer();
});

it('validates the expectations of WebFrontend', async () => {
const opts = {
provider: 'AuthService',
providerBaseUrl: `http://localhost:${port}`,
pactBrokerUrl: 'http://pact-broker:9292', // 从Pact Broker获取契约
pactUrls: [ './pacts/webfrontend-authservice.json' ], // 或本地文件路径
publishVerificationResult: true
};

return new Verifier().verifyProvider(opts)
.then(output => console.log('Pact Verification Complete!', output));
});
});

关键验证点说明

  1. 请求匹配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 消费者定义的请求必须精确匹配
    withRequest: {
    method: 'POST',
    path: '/approvals',
    headers: { 'Content-Type': 'application/json' },
    body: {
    userId: 'user123',
    groupId: 'group456'
    }
    }
    • 提供者的路由必须存在POST /approvals
    • 请求体必须包含userIdgroupId字段
  2. 响应匹配

    1
    2
    3
    4
    5
    6
    7
    willRespondWith: {
    status: 201,
    body: {
    id: Matchers.uuid('d1f0a3d4'), // 验证UUID格式
    status: 'PENDING' // 必须返回精确值
    }
    }
    • 提供者必须返回201状态码
    • 响应必须包含符合UUID格式的id字段

测试执行流程

  1. 消费者测试生成契约

    1
    2
    3
    4
    # 在前端项目中运行
    npm test -- tests/contract/auth-service.consumer.spec.js
    # 生成契约文件并发布到Pact Broker
    pact-broker publish ./pacts --consumer-app-version=1.0.0 --broker-base-url=http://broker
  2. 提供者验证契约

    1
    2
    # 在Auth Service项目中运行
    npm test -- tests/contract/auth-service.provider.spec.js
  3. 持续集成配置(示例GitHub Actions)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    jobs:
    consumer-tests:
    runs-on: ubuntu-latest
    steps:
    - run: npm test -- contracts/
    - run: pact-broker publish ./pacts --auto-detect-version-properties

    provider-tests:
    needs: consumer-tests
    runs-on: ubuntu-latest
    steps:
    - run: npm test -- provider.contracts.spec.js

契约测试的核心价值

  1. 接口稳定性

    • 当Auth Service修改API时(如字段重命名),契约测试会立即失败
    • 防止提供者意外破坏现有消费者
  2. 文档即测试

    1
    2
    3
    4
    5
    6
    7
    8
    // 生成的契约文件成为权威文档
    "response": {
    "status": "PENDING",
    "body": {
    "id": "d1f0a3d4-...",
    "status": "PENDING"
    }
    }
  3. 跨团队协作

    • 前端团队可独立定义期望的接口行为
    • 后端团队通过验证契约确保实现符合预期

通过这种方式,当Auth Service升级到v2时,只需运行契约测试即可验证是否与现有消费者兼容,显著降低微服务间的集成风险。