Entry/Exit
- Entry: Unit tests cover single-component logic; interactions between components need to be verified
- Exit: Integration tests pass + boundary contract verification
- Output path: Integration test results →
verify-workflow-review - Points to: After integration tests pass, proceed to
/review - Preload: CANON.md +
build-quality-tdd/SKILL.md
When not to use
- Only verifying pure functions or single-component internal logic
- Cross-component/service contracts have not changed, and existing integration coverage is still valid
- The current issue is missing spec or code quality review, not boundary interaction verification
Iron Law
When to write integration tests
Must write: API endpoints (request→response), database operations (ORM→real DB), cross-service calls, authentication flows, file upload/processing, Webhook handling
No need to write: pure function logic (unit tests), third-party SDK internal behavior, simple CRUD with no business logic
Test doubles priority
偏好顺序(从高到低):
1. 真实实现 → 最高信心,能抓到真实 bug
2. Fake → 依赖的内存版本(如内存数据库)
3. Stub → 返回固定数据,无行为
4. Mock → 验证方法调用 —— 仅用于无法控制副作用的边界
Mock only in the following scenarios: external payment gateways, email sending services, third-party APIs (uncontrollable, rate-limited, or cost money).
Never mock the database. Use a test database or transaction rollback.
Test structure
Arrange-Act-Assert (AAA)
describe('POST /tasks', () => {
it('创建任务并返回 201', async () => {
// Arrange: 设置测试场景
const user = await createTestUser();
const payload = { title: '买菜', priority: 'high' };
// Act: 执行被测动作
const response = await request(app)
.post('/tasks')
.auth(user.token)
.send(payload);
// Assert: 验证结果
expect(response.status).toBe(201);
expect(response.body.task.title).toBe('买菜');
expect(response.body.task.status).toBe('pending');
// 验证副作用: 确实写入了数据库
const saved = await db.tasks.findById(response.body.task.id);
expect(saved).not.toBeNull();
});
});
API integration test patterns
// Happy path
it('创建资源返回 201', ...);
// Validation errors
it('缺少必填字段返回 400', async () => {
const response = await request(app).post('/tasks').send({});
expect(response.status).toBe(400);
expect(response.body.error.code).toBe('VALIDATION_ERROR');
});
// Auth errors
it('未认证用户返回 401', async () => {
const response = await request(app).post('/tasks').send(validPayload);
expect(response.status).toBe(401);
});
// Permission errors
it('非所有者修改他人任务返回 403', async () => {
const task = await createTask({ ownerId: 'user-a' });
const response = await request(app)
.patch(`/tasks/${task.id}`)
.auth('user-b-token')
.send({ title: 'hacked' });
expect(response.status).toBe(403);
});
// Not found
it('修改不存在的资源返回 404', async () => {
const response = await request(app)
.patch('/tasks/non-existent-id')
.auth(user.token)
.send({ title: 'test' });
expect(response.status).toBe(404);
});
// Edge cases (边界)
it('标题超过最大长度返回 400', ...);
it('分页 pageSize 超过上限自动截断', ...);
it('并发创建相同幂等键返回同一资源', ...);
Cover for each endpoint: Happy → Validation → Auth → Permission → Not Found → Edge
Test isolation for database tests
// 方法 A: 事务回滚(首选 —— 快速、可靠)
let tx: Transaction;
beforeEach(async () => {
tx = await db.startTransaction();
});
afterEach(async () => {
await tx.rollback(); // 所有变更回滚,不污染数据库
});
// 方法 B: 测试数据库(事务回滚不可用时)
beforeEach(async () => {
await testDb.truncate(); // 清空测试库
});
Test naming conventions
// 格式: <动作> + <场景> + <预期>
it('拒绝没有标题的任务创建')
it('对未认证用户返回 401')
it('幂等创建 —— 相同幂等键返回同一资源')
it('并发更新时返回 409 冲突')
Verification evidence
Output or records must include:
- Input/Source: the spec, plan, code, feedback, or release context that was read.
- Actions performed: the checks, generation, fixes, exports, or release steps actually completed.
- Verification results: commands, review conclusions, artifact paths, screenshots, or manual confirmation.
- Blockers/Rollback: failed items, rollback paths, or issues requiring a human partner's decision.
Common excuses
| Excuse | Reality | Consequence |
|---|---|---|
| "Unit tests are enough" | Unit tests verify isolated units. Integration tests verify that they work correctly together. Will API parameters be passed correctly? Will the database accept the data? Unit tests do not answer these. | All unit tests green but integration failure is the most common production failure mode—parameter format mismatches, database constraint conflicts, broken authentication flows. These bugs are exposed only when users actually operate. |
| "Mocking external dependencies is faster" | Data returned by a mocked database ≠ data accepted by a real database. Use a test DB instead of a mock. | Mocks pass but the real database rejects the data (type mismatches, constraint violations, character encoding issues), exploding as 500 errors after launch. |
| "Writing integration tests is slow" | 500ms-2s per integration test. The debugging time saved by finding one cross-component bug far exceeds this. | A cross-component bug takes 4-8 hours to troubleshoot in production (log tracing + multi-service localization); an integration test written in 2 minutes can catch the same type of problem. |
| "Happy path is enough, error cases can be tested manually" | Manually testing error paths provides no regression protection. The next code change may inadvertently crash on the error path. | Untested error paths regress in a change 2 weeks later, appearing as 500s/empty responses/data inconsistencies, and cannot be detected automatically. |
Violating the letter of the rule is violating the spirit. There is no gray area.
Red flag — STOP
- Integration tests mock the database (use a real DB/transaction rollback instead)
- Tests only cover the Happy Path (do not test Validation/Auth/Permission/Not Found)
- Tests are not isolated from each other (test A's data affects test B's results)
- Integration tests call real external services (payment gateways, email) — these should be mocked
- Tests use
setTimeoutwaits (use conditional pollingwaitFor)
Good/Bad examples
Bad: only testing the Happy Path
describe('POST /tasks', () => {
it('创建任务成功', async () => {
const res = await request(app).post('/tasks').auth(token).send({ title: 'test' });
expect(res.status).toBe(201);
});
});
Problem: no verification of 400 (missing fields), 401 (unauthenticated), 403 (no permission), 404 (not found), or edge cases. After launch, these paths will be exposed as 500 errors.
Good: complete endpoint coverage
describe('POST /tasks', () => {
it('创建任务返回 201', async () => { /* happy path */ });
it('缺少 title 返回 400 VALIDATION_ERROR', async () => { /* validation */ });
it('未认证返回 401', async () => { /* auth */ });
it('非所有者修改他人任务返回 403', async () => { /* permission */ });
it('修改不存在任务返回 404', async () => { /* not found */ });
it('幂等键重复返回同一资源', async () => { /* edge: idempotency */ });
});
Advantages: covers Happy + Validation + Auth + Permission + Not Found + Edge, with regression protection for every path.
Validation failure handling
| Failure scenario | Handling method |
|---|---|
| Integration tests all red (Happy Path fails) | Block; fix the core functionality first before testing other paths. Do not skip the Happy Path and move to review |
| Auth/Permission tests fail | Block; authentication and permission issues are security red lines and must not be downgraded to "fix later" |
| Database isolation fails (state leaks between tests) | Fix the isolation strategy (transaction rollback or truncate); do not rely on manual cleanup. Shared state between tests = untrustworthy results |
| External service mocks are inconsistent with real behavior | Update the mock behavior to match the real API documentation, or replace it with a Fake implementation. Mock inconsistent with reality = invalid test |
| Some endpoints are not covered by tests | Add the missing endpoint tests before review; do not accept "add tests later". Uncovered endpoints = unverified contracts |
Output template
# Integration Test Report — <name>
## 覆盖范围
- 端点数: N
- 覆盖端点: [列表]
- 未覆盖端点: [列表 + 原因]
## 测试矩阵
| 端点 | Happy | Validation | Auth | Permission | Not Found | Edge |
|------|-------|------------|------|------------|-----------|------|
| POST /tasks | PASS | PASS | PASS | PASS | — | PASS |
| PATCH /tasks/:id | PASS | PASS | PASS | PASS | PASS | PASS |
## 测试替身使用
| 依赖 | 替身类型 | 原因 |
|------|---------|------|
| 数据库 | 真实(事务回滚) | 绝不 mock 数据库 |
| 支付网关 | Mock | 外部服务,不可控 |
| 邮件服务 | Mock | 外部服务,有副作用 |
## 隔离策略
- 方式: 事务回滚 / truncate
- 验证: 测试间无共享状态
## 结果
- 通过数: X
- 失败数: Y
- 跳过数: Z
## 失败详情
[列出每个失败测试:端点、路径、失败原因、修复建议]
## 下一步
- 全绿 → 进入 /review
- 有失败 → 修复后重新运行,不跳过进入 review
Verification checklist
- Every API endpoint has Happy + Validation + Auth + Not Found tests
- Permission-sensitive endpoints have 403 tests
- Database tests are isolated with transaction rollback
- External services (payment/email) are mocked
- There is no shared state between tests
- Test names describe behavior and expectations