Koa 项目单元测试与接口测试实战
测试策略分层
一个健壮的 Koa 项目应包含三层测试:单元测试(Unit Test)验证独立函数逻辑,集成测试(Integration Test)验证模块协作,端到端测试(E2E)验证完整业务流程。本文重点介绍前两层在 Koa 项目中的落地实践。
测试工具选型
| 工具 | 用途 | 说明 |
|---|---|---|
| Jest | 测试框架 | 内置断言、Mock、覆盖率 |
| SuperTest | HTTP 测试 | 无需启动服务器即可测试接口 |
| sqlite3 | 内存数据库 | 测试环境快速初始化数据 |
项目目录结构
koa-project/
├── src/
│ ├── app.js # Koa 应用实例
│ ├── routes/
│ ├── services/
│ └── models/
├── tests/
│ ├── unit/ # 单元测试
│ └── integration/ # 接口测试
├── jest.config.js
└── package.json
单元测试示例
测试 Service 层的业务逻辑,不依赖 HTTP 和数据库:
// src/services/user.js
class UserService {
static validatePassword(password) {
return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(password);
}
static calculateLevel(score) {
if (score >= 10000) return 'VIP';
if (score >= 5000) return 'Gold';
if (score >= 1000) return 'Silver';
return 'Normal';
}
}
// tests/unit/user.test.js
const UserService = require('../../src/services/user');
describe('UserService', () => {
test('密码强度校验', () => {
expect(UserService.validatePassword('weak')).toBe(false);
expect(UserService.validatePassword('Strong1Pass')).toBe(true);
});
test('用户等级计算', () => {
expect(UserService.calculateLevel(800)).toBe('Normal');
expect(UserService.calculateLevel(3000)).toBe('Silver');
expect(UserService.calculateLevel(6000)).toBe('Gold');
expect(UserService.calculateLevel(15000)).toBe('VIP');
});
});
接口测试示例
使用 SuperTest 测试 HTTP 接口,配合内存数据库实现隔离:
// tests/integration/user.test.js
const request = require('supertest');
const app = require('../../src/app');
describe('用户接口', () => {
test('注册用户', async () => {
const res = await request(app.callback())
.post('/api/users/register')
.send({
username: 'testuser',
password: 'Test1234',
email: 'test@example.com'
});
expect(res.status).toBe(201);
expect(res.body).toHaveProperty('id');
expect(res.body.username).toBe('testuser');
});
test('登录并获取 Token', async () => {
const res = await request(app.callback())
.post('/api/users/login')
.send({
username: 'testuser',
password: 'Test1234'
});
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('token');
global.token = res.body.token;
});
test('获取当前用户信息', async () => {
const res = await request(app.callback())
.get('/api/users/me')
.set('Authorization', `Bearer ${global.token}`);
expect(res.status).toBe(200);
expect(res.body.username).toBe('testuser');
});
test('未授权访问应返回 401', async () => {
const res = await request(app.callback())
.get('/api/users/me');
expect(res.status).toBe(401);
});
});
Jest 配置
// jest.config.js
module.exports = {
testEnvironment: 'node',
coverageDirectory: 'coverage',
collectCoverageFrom: [
'src/**/*.js',
'!src/app.js'
],
coverageThreshold: {
global: {
branches: 70,
functions: 80,
lines: 80,
statements: 80
}
},
setupFilesAfterEnv: ['./tests/setup.js']
};
Mock 外部依赖
测试时不应调用真实的第三方服务:
// tests/unit/sms.test.js
const smsService = require('../../src/services/sms');
jest.mock('../../src/utils/smsProvider', () => ({
send: jest.fn().mockResolvedValue({ code: 'OK', messageId: 'mock-id' })
}));
test('发送验证码', async () => {
const result = await smsService.sendVerifyCode('13800138000');
expect(result.success).toBe(true);
});
CI 集成
在 GitLab CI 中自动运行测试并生成覆盖率报告:
test:
stage: test
script:
- npm ci
- npm run test:coverage
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
artifacts:
paths:
- coverage/
总结
完善的测试体系是项目质量的保障。建议核心 Service 层达到 80% 以上覆盖率,接口测试覆盖所有正常和异常场景。测试代码应与业务代码同等重视,保持可读性和可维护性。