integration-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Integration Testing Skill

集成测试Skill

This skill captures critical learnings from US-001 (birdmate project) where 3 integration bugs appeared in demo despite 272 passing unit tests. Covers CORS configuration, API contract validation, and build artifact hygiene.
本Skill总结了US-001(birdmate项目)中的关键经验:尽管有272个通过的单元测试,但演示中仍出现了3个集成bug。内容涵盖CORS配置、API契约验证及构建产物规范。

CORS Development Configuration

CORS开发配置

Critical Pattern: Development CORS must accommodate dynamic port assignment
typescript
// ❌ WRONG - Single hardcoded origin
const CORS_ORIGIN = 'http://localhost:5173';

// ✅ CORRECT - Support port range
const CORS_ORIGINS = (process.env.CORS_ORIGIN || 
  'http://localhost:5173,http://localhost:5174,http://localhost:5175'
).split(',').map(s => s.trim());

app.use(cors({ origin: CORS_ORIGINS, credentials: true }));
Why: Vite/dev servers auto-increment ports when default is occupied
Configuration Template:
bash
undefined
关键模式:开发环境下的CORS必须支持动态端口分配
typescript
// ❌ 错误 - 单一硬编码源
const CORS_ORIGIN = 'http://localhost:5173';

// ✅ 正确 - 支持端口范围
const CORS_ORIGINS = (process.env.CORS_ORIGIN || 
  'http://localhost:5173,http://localhost:5174,http://localhost:5175'
).split(',').map(s => s.trim());

app.use(cors({ origin: CORS_ORIGINS, credentials: true }));
原因:当默认端口被占用时,Vite/开发服务器会自动递增端口
配置模板:
bash
undefined

backend/.env

backend/.env

backend/.env.example (commit this)

backend/.env.example(提交此文件)

API Contract Validation

API契约验证

Critical: Types at integration boundary must match wire format
关键要点:集成边界的类型必须与传输格式匹配

Anti-Pattern: Over-abstraction

反模式:过度抽象

typescript
// Shared type (ideal design, not actual API)
interface SearchResult {
  bird: Bird;  // ❌ Nested structure
  score: number;
}
typescript
// 共享类型(理想设计,非实际API)
interface SearchResult {
  bird: Bird;  // ❌ 嵌套结构
  score: number;
}

Correct Pattern: Wire-format types

正确模式:传输格式类型

typescript
// API response types (match actual backend)
interface ApiSearchResult {
  id: string;         // ✅ Flat structure
  commonName: string;
  score: number;
}

// Transform at boundary
function transformResults(apiResults: ApiSearchResult[]): AppResult[] {
  return apiResults.map(api => ({
    bird: { id: api.id, commonName: api.commonName },
    score: api.score
  }));
}
Validation Checklist:
  • Test API endpoint with curl, inspect actual JSON
  • Create
    Api*
    types matching exact response structure
  • Transform to app types at integration boundary only
  • Never assume shared types match wire format
typescript
// API响应类型(与实际后端匹配)
interface ApiSearchResult {
  id: string;         // ✅ 扁平结构
  commonName: string;
  score: number;
}

// 在边界处转换
function transformResults(apiResults: ApiSearchResult[]): AppResult[] {
  return apiResults.map(api => ({
    bird: { id: api.id, commonName: api.commonName },
    score: api.score
  }));
}
验证检查清单:
  • 使用curl测试API端点,检查实际JSON结构
  • 创建与响应结构完全匹配的
    Api*
    类型
  • 仅在集成边界处转换为应用类型
  • 切勿假设共享类型与传输格式一致

Build Artifact Hygiene

构建产物规范

Critical: Compiled files must not shadow source files
关键要点:编译文件不得覆盖源文件

TypeScript Project .gitignore

TypeScript项目.gitignore

gitignore
undefined
gitignore
undefined

Compiled outputs

编译输出

dist/ build/ *.js # ⚠️ CRITICAL in src/ directories *.js.map *.jsx *.jsx.map
dist/ build/ *.js # ⚠️ 在src/目录中至关重要 *.js.map *.jsx *.jsx.map

Exception: config files (use negation)

例外:配置文件(使用否定规则)

!vite.config.js !playwright.config.js !*.config.js

**Pre-demo validation**:
```bash
!vite.config.js !playwright.config.js !*.config.js

**预演示验证**:
```bash

Check for stale .js in src/

检查src/中的旧.js文件

find src/ -name "*.js" -type f | grep -v node_modules
find src/ -name "*.js" -type f | grep -v node_modules

Should return empty or only intentional .js files

应返回空或仅包含有意保留的.js文件


**Package.json clean scripts**:
```json
{
  "scripts": {
    "clean": "find src/ -name '*.js' -o -name '*.jsx' | xargs rm -f",
    "prebuild": "npm run clean",
    "predev": "npm run clean"
  }
}

**Package.json清理脚本**:
```json
{
  "scripts": {
    "clean": "find src/ -name '*.js' -o -name '*.jsx' | xargs rm -f",
    "prebuild": "npm run clean",
    "predev": "npm run clean"
  }
}

Integration Testing Workflow

集成测试流程

1. Start Backend

1. 启动后端

bash
cd backend && npm run dev
Verify CORS origins logged at startup
bash
cd backend && npm run dev
验证启动日志中是否包含配置的CORS源

2. Start Frontend

2. 启动前端

bash
cd frontend && npm run dev
Note actual port assigned (may not be 5173)
bash
cd frontend && npm run dev
记录实际分配的端口(可能不是5173)

3. Test CORS Preflight

3. 测试CORS预检

bash
curl -I -X OPTIONS http://localhost:3001/api/v1/search \
  -H "Origin: http://localhost:5175" \
  -H "Access-Control-Request-Method: POST"
bash
curl -I -X OPTIONS http://localhost:3001/api/v1/search \
  -H "Origin: http://localhost:5175" \
  -H "Access-Control-Request-Method: POST"

Should return: Access-Control-Allow-Origin: http://localhost:5175

应返回:Access-Control-Allow-Origin: http://localhost:5175

undefined
undefined

4. Test API Contract

4. 测试API契约

bash
curl -X POST http://localhost:3001/api/v1/search \
  -H "Content-Type: application/json" \
  -d '{"query": "test"}' | jq '.' > actual-response.json
bash
curl -X POST http://localhost:3001/api/v1/search \
  -H "Content-Type: application/json" \
  -d '{"query": "test"}' | jq '.' > actual-response.json

Verify response structure matches frontend types

验证响应结构与前端类型匹配

undefined
undefined

5. Browser Network Inspection

5. 浏览器网络检查

  • Open DevTools → Network tab
  • Execute feature in browser
  • Verify no CORS errors (200 OK on OPTIONS + POST)
  • Verify no type errors in console
  • 打开开发者工具 → 网络标签
  • 在浏览器中执行功能操作
  • 验证无CORS错误(OPTIONS和POST请求均返回200 OK)
  • 验证控制台无类型错误

Common Failure Modes

常见故障模式

SymptomRoot CauseFix
ERR_FAILED
network request
CORS misconfigurationAdd frontend port to backend CORS_ORIGIN
Access-Control-Allow-Origin
error
Single port in CORS, frontend on different portUse comma-separated port list
Cannot read property 'x' of undefined
Type mismatch (nested vs flat)Create API-specific types at boundary
Old UI showing after code changesStale .js files shadowing .tsxRemove compiled artifacts from src/
Port already in usePrevious dev server still running
lsof -ti:3001 | xargs kill -9
症状根本原因修复方案
ERR_FAILED
网络请求
CORS配置错误将前端端口添加到后端的CORS_ORIGIN中
Access-Control-Allow-Origin
错误
CORS仅配置了单一端口,前端使用不同端口使用逗号分隔的端口列表
Cannot read property 'x' of undefined
类型不匹配(嵌套vs扁平)在边界处创建API专用类型
代码更改后仍显示旧UI旧.js文件覆盖.tsx文件删除src/中的编译产物
端口已被占用之前的开发服务器仍在运行
lsof -ti:3001 | xargs kill -9

Pre-Demo Validation Checklist

预演示验证检查清单

Before demoing any full-stack feature:
  • Backend
    .env
    has
    CORS_ORIGIN
    with port range (5173-5175)
  • No
    .js
    or
    .jsx
    files in
    frontend/src/
    or
    backend/src/
  • curl
    test confirms API response structure
  • Network tab shows successful OPTIONS + POST requests
  • Console shows no CORS or type errors
  • URL state persistence working (if applicable)
  • Both services started fresh (no cached state)
在演示任何全栈功能之前:
  • 后端
    .env
    文件中的
    CORS_ORIGIN
    包含端口范围(5173-5175)
  • frontend/src/
    backend/src/
    中无
    .js
    .jsx
    文件
  • curl测试确认API响应结构正确
  • 网络标签显示OPTIONS和POST请求均成功
  • 控制台无CORS或类型错误
  • URL状态持久化功能正常(如适用)
  • 两个服务均为全新启动(无缓存状态)

Integration Test Template

集成测试模板

typescript
// e2e/integration-smoke.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Full Stack Integration', () => {
  test.beforeAll(async () => {
    // Verify backend is running
    const health = await fetch('http://localhost:3001/health');
    expect(health.ok).toBeTruthy();
  });
  
  test('user can complete primary user flow', async ({ page }) => {
    // Monitor console for errors
    const consoleErrors: string[] = [];
    page.on('console', msg => {
      if (msg.type() === 'error') {
        consoleErrors.push(msg.text());
      }
    });
    
    // Execute user flow
    await page.goto('http://localhost:5173');
    await page.getByPlaceholder('Search...').fill('test query');
    await page.getByRole('button', { name: 'Search' }).click();
    
    // Verify success
    await expect(page.getByTestId('results')).toBeVisible();
    
    // Assert no integration errors
    expect(consoleErrors).toHaveLength(0);
    expect(consoleErrors.filter(e => e.includes('CORS'))).toHaveLength(0);
    expect(consoleErrors.filter(e => e.includes('undefined'))).toHaveLength(0);
  });
});
typescript
// e2e/integration-smoke.spec.ts
import { test, expect } from '@playwright/test';

test.describe('全栈集成', () => {
  test.beforeAll(async () => {
    // 验证后端是否运行
    const health = await fetch('http://localhost:3001/health');
    expect(health.ok).toBeTruthy();
  });
  
  test('用户可完成主要用户流程', async ({ page }) => {
    // 监控控制台错误
    const consoleErrors: string[] = [];
    page.on('console', msg => {
      if (msg.type() === 'error') {
        consoleErrors.push(msg.text());
      }
    });
    
    // 执行用户流程
    await page.goto('http://localhost:5173');
    await page.getByPlaceholder('Search...').fill('test query');
    await page.getByRole('button', { name: 'Search' }).click();
    
    // 验证成功
    await expect(page.getByTestId('results')).toBeVisible();
    
    // 断言无集成错误
    expect(consoleErrors).toHaveLength(0);
    expect(consoleErrors.filter(e => e.includes('CORS'))).toHaveLength(0);
    expect(consoleErrors.filter(e => e.includes('undefined'))).toHaveLength(0);
  });
});

When to Use This Skill

何时使用本Skill

  • Full-stack TypeScript projects with separate frontend/backend
  • When backend and frontend run on different ports/origins
  • Debugging CORS issues in development
  • Verifying API contract alignment between services
  • Before demoing features to users
  • Setting up new full-stack projects
  • 前后端分离的全栈TypeScript项目
  • 后端与前端运行在不同端口/源的场景
  • 调试开发环境中的CORS问题
  • 验证服务间的API契约一致性
  • 向用户演示功能之前
  • 搭建新的全栈项目时

Related Skills

相关Skill

  • tdd-workflow - Unit testing discipline
  • playwright-testing - E2E test standards
  • fullstack-expertise - Full-stack development patterns
  • tdd-workflow - 单元测试规范
  • playwright-testing - E2E测试标准
  • fullstack-expertise - 全栈开发模式