backend-testing by supercent-io/skills-template
npx skills add https://github.com/supercent-io/skills-template --skill backend-testing应触发此技能的具体情况:
从用户处收集信息的格式和必需/可选信息:
测试 Express.js API 的用户认证端点:
- 框架:Express + TypeScript
- 测试工具:Jest + Supertest
- 目标:POST /auth/register, POST /auth/login
- 数据库:PostgreSQL(测试时使用内存数据库)
- 覆盖率:90% 或以上
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
需严格遵循的逐步任务顺序。
安装并配置测试框架和工具。
任务:
示例(Node.js + Jest + Supertest):
npm install --save-dev jest ts-jest @types/jest supertest @types/supertest
jest.config.js:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.test.ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/__tests__/**'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts']
};
setup.ts(全局测试配置):
import { db } from '../database';
// 在每个测试前重置数据库
beforeEach(async () => {
await db.migrate.latest();
await db.seed.run();
});
// 在每个测试后清理
afterEach(async () => {
await db.migrate.rollback();
});
// 所有测试完成后关闭连接
afterAll(async () => {
await db.destroy();
});
为独立的函数和类编写单元测试。
任务:
决策标准:
示例(密码验证函数):
// src/utils/password.ts
export function validatePassword(password: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain lowercase letter');
}
if (!/\d/.test(password)) {
errors.push('Password must contain number');
}
if (!/[!@#$%^&*]/.test(password)) {
errors.push('Password must contain special character');
}
return { valid: errors.length === 0, errors };
}
// src/__tests__/utils/password.test.ts
import { validatePassword } from '../../utils/password';
describe('validatePassword', () => {
it('should accept valid password', () => {
const result = validatePassword('Password123!');
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should reject password shorter than 8 characters', () => {
const result = validatePassword('Pass1!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must be at least 8 characters');
});
it('should reject password without uppercase', () => {
const result = validatePassword('password123!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain uppercase letter');
});
it('should reject password without lowercase', () => {
const result = validatePassword('PASSWORD123!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain lowercase letter');
});
it('should reject password without number', () => {
const result = validatePassword('Password!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain number');
});
it('should reject password without special character', () => {
const result = validatePassword('Password123');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain special character');
});
it('should return multiple errors for invalid password', () => {
const result = validatePassword('pass');
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThan(1);
});
});
为 API 端点编写集成测试。
任务:
检查清单:
示例(Express.js + Supertest):
// src/__tests__/api/auth.test.ts
import request from 'supertest';
import app from '../../app';
import { db } from '../../database';
describe('POST /auth/register', () => {
it('should register new user successfully', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'testuser',
password: 'Password123!'
});
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('user');
expect(response.body).toHaveProperty('accessToken');
expect(response.body.user.email).toBe('test@example.com');
// 验证记录确实保存到了数据库
const user = await db.user.findUnique({ where: { email: 'test@example.com' } });
expect(user).toBeTruthy();
expect(user.username).toBe('testuser');
});
it('should reject duplicate email', async () => {
// 创建第一个用户
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'user1',
password: 'Password123!'
});
// 使用相同邮箱的第二次尝试
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'user2',
password: 'Password123!'
});
expect(response.status).toBe(409);
expect(response.body.error).toContain('already exists');
});
it('should reject weak password', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'testuser',
password: 'weak'
});
expect(response.status).toBe(400);
expect(response.body.error).toBeDefined();
});
it('should reject missing fields', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com'
// username, password omitted
});
expect(response.status).toBe(400);
});
});
describe('POST /auth/login', () => {
beforeEach(async () => {
// 创建测试用户
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'testuser',
password: 'Password123!'
});
});
it('should login with valid credentials', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'Password123!'
});
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('accessToken');
expect(response.body).toHaveProperty('refreshToken');
expect(response.body.user.email).toBe('test@example.com');
});
it('should reject invalid password', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'WrongPassword123!'
});
expect(response.status).toBe(401);
expect(response.body.error).toContain('Invalid credentials');
});
it('should reject non-existent user', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'nonexistent@example.com',
password: 'Password123!'
});
expect(response.status).toBe(401);
});
});
测试 JWT 令牌和基于角色的访问控制。
任务:
示例:
describe('Protected Routes', () => {
let accessToken: string;
let adminToken: string;
beforeEach(async () => {
// 普通用户令牌
const userResponse = await request(app)
.post('/api/auth/register')
.send({
email: 'user@example.com',
username: 'user',
password: 'Password123!'
});
accessToken = userResponse.body.accessToken;
// 管理员令牌
const adminResponse = await request(app)
.post('/api/auth/register')
.send({
email: 'admin@example.com',
username: 'admin',
password: 'Password123!'
});
// 在数据库中更新角色为 'admin'
await db.user.update({
where: { email: 'admin@example.com' },
data: { role: 'admin' }
});
// 再次登录以获取新令牌
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: 'admin@example.com',
password: 'Password123!'
});
adminToken = loginResponse.body.accessToken;
});
describe('GET /api/auth/me', () => {
it('should return current user with valid token', async () => {
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', `Bearer ${accessToken}`);
expect(response.status).toBe(200);
expect(response.body.user.email).toBe('user@example.com');
});
it('should reject request without token', async () => {
const response = await request(app)
.get('/api/auth/me');
expect(response.status).toBe(401);
});
it('should reject request with invalid token', async () => {
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', 'Bearer invalid-token');
expect(response.status).toBe(403);
});
});
describe('DELETE /api/users/:id (Admin only)', () => {
it('should allow admin to delete user', async () => {
const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });
const response = await request(app)
.delete(`/api/users/${targetUser.id}`)
.set('Authorization', `Bearer ${adminToken}`);
expect(response.status).toBe(200);
});
it('should forbid non-admin from deleting user', async () => {
const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });
const response = await request(app)
.delete(`/api/users/${targetUser.id}`)
.set('Authorization', `Bearer ${accessToken}`);
expect(response.status).toBe(403);
});
});
});
模拟外部依赖以隔离测试。
任务:
示例(模拟外部 API):
// src/services/emailService.ts
export async function sendVerificationEmail(email: string, token: string): Promise<void> {
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}` },
body: JSON.stringify({
to: email,
subject: 'Verify your email',
html: `<a href="https://example.com/verify?token=${token}">Verify</a>`
})
});
if (!response.ok) {
throw new Error('Failed to send email');
}
}
// src/__tests__/services/emailService.test.ts
import { sendVerificationEmail } from '../../services/emailService';
// 模拟 fetch
global.fetch = jest.fn();
describe('sendVerificationEmail', () => {
beforeEach(() => {
(fetch as jest.Mock).mockClear();
});
it('should send email successfully', async () => {
(fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
status: 200
});
await expect(sendVerificationEmail('test@example.com', 'token123'))
.resolves
.toBeUndefined();
expect(fetch).toHaveBeenCalledWith(
'https://api.sendgrid.com/v3/mail/send',
expect.objectContaining({
method: 'POST'
})
);
});
it('should throw error if email sending fails', async () => {
(fetch as jest.Mock).mockResolvedValueOnce({
ok: false,
status: 500
});
await expect(sendVerificationEmail('test@example.com', 'token123'))
.rejects
.toThrow('Failed to send email');
});
});
定义输出必须遵循的确切格式。
project/
├── src/
│ ├── __tests__/
│ │ ├── setup.ts # 全局测试配置
│ │ ├── utils/
│ │ │ └── password.test.ts # 单元测试
│ │ ├── services/
│ │ │ └── emailService.test.ts
│ │ └── api/
│ │ ├── auth.test.ts # 集成测试
│ │ └── users.test.ts
│ └── ...
├── jest.config.js
└── package.json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage --maxWorkers=2"
}
}
$ npm run test:coverage
--------------------------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
--------------------------|---------|----------|---------|---------|
All files | 92.5 | 88.3 | 95.2 | 92.8 |
auth/ | 95.0 | 90.0 | 100.0 | 95.0 |
middleware.ts | 95.0 | 90.0 | 100.0 | 95.0 |
routes.ts | 95.0 | 90.0 | 100.0 | 95.0 |
utils/ | 90.0 | 85.0 | 90.0 | 90.0 |
password.ts | 90.0 | 85.0 | 90.0 | 90.0 |
--------------------------|---------|----------|---------|---------|
必须严格遵守的规则和禁止事项。
情况:测试使用 FastAPI 构建的 REST API
用户请求:
使用 pytest 测试使用 FastAPI 构建的用户 API。
最终结果:
# tests/conftest.py
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.main import app
from app.database import Base, get_db
# 用于测试的内存 SQLite
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@pytest.fixture(scope="function")
def db_session():
Base.metadata.create_all(bind=engine)
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
Base.metadata.drop_all(bind=engine)
@pytest.fixture(scope="function")
def client(db_session):
def override_get_db():
try:
yield db_session
finally:
db_session.close()
app.dependency_overrides[get_db] = override_get_db
yield TestClient(app)
app.dependency_overrides.clear()
# tests/test_auth.py
def test_register_user_success(client):
response = client.post("/auth/register", json={
"email": "test@example.com",
"username": "testuser",
"password": "Password123!"
})
assert response.status_code == 201
assert "access_token" in response.json()
assert response.json()["user"]["email"] == "test@example.com"
def test_register_duplicate_email(client):
# 第一个用户
client.post("/auth/register", json={
"email": "test@example.com",
"username": "user1",
"password": "Password123!"
})
# 重复邮箱
response = client.post("/auth/register", json={
"email": "test@example.com",
"username": "user2",
"password": "Password123!"
})
assert response.status_code == 409
assert "already exists" in response.json()["detail"]
def test_login_success(client):
# 注册
client.post("/auth/register", json={
"email": "test@example.com",
"username": "testuser",
"password": "Password123!"
})
# 登录
response = client.post("/auth/login", json={
"email": "test@example.com",
"password": "Password123!"
})
assert response.status_code == 200
assert "access_token" in response.json()
def test_protected_route_without_token(client):
response = client.get("/auth/me")
assert response.status_code == 401
def test_protected_route_with_token(client):
# 注册并获取令牌
register_response = client.post("/auth/register", json={
"email": "test@example.com",
"username": "testuser",
"password": "Password123!"
})
token = register_response.json()["access_token"]
# 访问受保护路由
response = client.get("/auth/me", headers={
"Authorization": f"Bearer {token}"
})
assert response.status_code == 200
assert response.json()["email"] == "test@example.com"
TDD(测试驱动开发):在编写代码之前先编写测试
Given-When-Then 模式:以 BDD 风格编写测试
it('should return 404 when user not found', async () => {
// Given: 一个不存在的用户 ID
const nonExistentId = 'non-existent-uuid';
// When: 尝试查找该用户
const response = await request(app).get(`/users/${nonExistentId}`);
// Then: 404 响应
expect(response.status).toBe(404);
});
测试夹具:可重用的测试数据
const validUser = {
email: 'test@example.com',
username: 'testuser',
password: 'Password123!'
};
--maxWorkers 选项加速测试症状:单独运行时通过,但一起运行时失败
原因:由于缺少 beforeEach/afterEach 导致数据库状态共享
修复方法:
beforeEach(async () => {
await db.migrate.rollback();
await db.migrate.latest();
});
症状:测试完成后进程不退出
原因:数据库连接、服务器等未清理
修复方法:
afterAll(async () => {
await db.destroy();
await server.close();
});
症状:"超时 - 未调用异步回调"
原因:缺少 async/await 或未处理的 Promise
修复方法:
// 错误示例
it('should work', () => {
request(app).get('/users'); // Promise 未处理
});
// 正确示例
it('should work', async () => {
await request(app).get('/users');
});
#testing #backend #Jest #Pytest #unit-test #integration-test #TDD #API-test
每周安装数
11.8K
代码仓库
GitHub 星标数
88
首次出现
Jan 24, 2026
安全审计
安装于
codex11.7K
gemini-cli11.6K
opencode11.6K
github-copilot11.6K
cursor11.6K
amp11.6K
Specific situations that should trigger this skill:
Format and required/optional information to collect from the user:
Test the user authentication endpoints for an Express.js API:
- Framework: Express + TypeScript
- Test tool: Jest + Supertest
- Target: POST /auth/register, POST /auth/login
- DB: PostgreSQL (in-memory for tests)
- Coverage: 90% or above
Step-by-step task order to follow precisely.
Install and configure the test framework and tools.
Tasks :
Example (Node.js + Jest + Supertest):
npm install --save-dev jest ts-jest @types/jest supertest @types/supertest
jest.config.js :
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.test.ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/__tests__/**'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts']
};
setup.ts (global test configuration):
import { db } from '../database';
// Reset DB before each test
beforeEach(async () => {
await db.migrate.latest();
await db.seed.run();
});
// Clean up after each test
afterEach(async () => {
await db.migrate.rollback();
});
// Close connection after all tests complete
afterAll(async () => {
await db.destroy();
});
Write unit tests for individual functions and classes.
Tasks :
Decision criteria :
Example (password validation function):
// src/utils/password.ts
export function validatePassword(password: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain lowercase letter');
}
if (!/\d/.test(password)) {
errors.push('Password must contain number');
}
if (!/[!@#$%^&*]/.test(password)) {
errors.push('Password must contain special character');
}
return { valid: errors.length === 0, errors };
}
// src/__tests__/utils/password.test.ts
import { validatePassword } from '../../utils/password';
describe('validatePassword', () => {
it('should accept valid password', () => {
const result = validatePassword('Password123!');
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should reject password shorter than 8 characters', () => {
const result = validatePassword('Pass1!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must be at least 8 characters');
});
it('should reject password without uppercase', () => {
const result = validatePassword('password123!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain uppercase letter');
});
it('should reject password without lowercase', () => {
const result = validatePassword('PASSWORD123!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain lowercase letter');
});
it('should reject password without number', () => {
const result = validatePassword('Password!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain number');
});
it('should reject password without special character', () => {
const result = validatePassword('Password123');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain special character');
});
it('should return multiple errors for invalid password', () => {
const result = validatePassword('pass');
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThan(1);
});
});
Write integration tests for API endpoints.
Tasks :
Checklist :
Example (Express.js + Supertest):
// src/__tests__/api/auth.test.ts
import request from 'supertest';
import app from '../../app';
import { db } from '../../database';
describe('POST /auth/register', () => {
it('should register new user successfully', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'testuser',
password: 'Password123!'
});
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('user');
expect(response.body).toHaveProperty('accessToken');
expect(response.body.user.email).toBe('test@example.com');
// Verify the record was actually saved to DB
const user = await db.user.findUnique({ where: { email: 'test@example.com' } });
expect(user).toBeTruthy();
expect(user.username).toBe('testuser');
});
it('should reject duplicate email', async () => {
// Create first user
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'user1',
password: 'Password123!'
});
// Second attempt with same email
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'user2',
password: 'Password123!'
});
expect(response.status).toBe(409);
expect(response.body.error).toContain('already exists');
});
it('should reject weak password', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'testuser',
password: 'weak'
});
expect(response.status).toBe(400);
expect(response.body.error).toBeDefined();
});
it('should reject missing fields', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com'
// username, password omitted
});
expect(response.status).toBe(400);
});
});
describe('POST /auth/login', () => {
beforeEach(async () => {
// Create test user
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'testuser',
password: 'Password123!'
});
});
it('should login with valid credentials', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'Password123!'
});
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('accessToken');
expect(response.body).toHaveProperty('refreshToken');
expect(response.body.user.email).toBe('test@example.com');
});
it('should reject invalid password', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'WrongPassword123!'
});
expect(response.status).toBe(401);
expect(response.body.error).toContain('Invalid credentials');
});
it('should reject non-existent user', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'nonexistent@example.com',
password: 'Password123!'
});
expect(response.status).toBe(401);
});
});
Test JWT tokens and role-based access control.
Tasks :
Example :
describe('Protected Routes', () => {
let accessToken: string;
let adminToken: string;
beforeEach(async () => {
// Regular user token
const userResponse = await request(app)
.post('/api/auth/register')
.send({
email: 'user@example.com',
username: 'user',
password: 'Password123!'
});
accessToken = userResponse.body.accessToken;
// Admin token
const adminResponse = await request(app)
.post('/api/auth/register')
.send({
email: 'admin@example.com',
username: 'admin',
password: 'Password123!'
});
// Update role to 'admin' in DB
await db.user.update({
where: { email: 'admin@example.com' },
data: { role: 'admin' }
});
// Log in again to get a new token
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: 'admin@example.com',
password: 'Password123!'
});
adminToken = loginResponse.body.accessToken;
});
describe('GET /api/auth/me', () => {
it('should return current user with valid token', async () => {
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', `Bearer ${accessToken}`);
expect(response.status).toBe(200);
expect(response.body.user.email).toBe('user@example.com');
});
it('should reject request without token', async () => {
const response = await request(app)
.get('/api/auth/me');
expect(response.status).toBe(401);
});
it('should reject request with invalid token', async () => {
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', 'Bearer invalid-token');
expect(response.status).toBe(403);
});
});
describe('DELETE /api/users/:id (Admin only)', () => {
it('should allow admin to delete user', async () => {
const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });
const response = await request(app)
.delete(`/api/users/${targetUser.id}`)
.set('Authorization', `Bearer ${adminToken}`);
expect(response.status).toBe(200);
});
it('should forbid non-admin from deleting user', async () => {
const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });
const response = await request(app)
.delete(`/api/users/${targetUser.id}`)
.set('Authorization', `Bearer ${accessToken}`);
expect(response.status).toBe(403);
});
});
});
Mock external dependencies to isolate tests.
Tasks :
Example (mocking an external API):
// src/services/emailService.ts
export async function sendVerificationEmail(email: string, token: string): Promise<void> {
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}` },
body: JSON.stringify({
to: email,
subject: 'Verify your email',
html: `<a href="https://example.com/verify?token=${token}">Verify</a>`
})
});
if (!response.ok) {
throw new Error('Failed to send email');
}
}
// src/__tests__/services/emailService.test.ts
import { sendVerificationEmail } from '../../services/emailService';
// Mock fetch
global.fetch = jest.fn();
describe('sendVerificationEmail', () => {
beforeEach(() => {
(fetch as jest.Mock).mockClear();
});
it('should send email successfully', async () => {
(fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
status: 200
});
await expect(sendVerificationEmail('test@example.com', 'token123'))
.resolves
.toBeUndefined();
expect(fetch).toHaveBeenCalledWith(
'https://api.sendgrid.com/v3/mail/send',
expect.objectContaining({
method: 'POST'
})
);
});
it('should throw error if email sending fails', async () => {
(fetch as jest.Mock).mockResolvedValueOnce({
ok: false,
status: 500
});
await expect(sendVerificationEmail('test@example.com', 'token123'))
.rejects
.toThrow('Failed to send email');
});
});
Defines the exact format that outputs must follow.
project/
├── src/
│ ├── __tests__/
│ │ ├── setup.ts # Global test configuration
│ │ ├── utils/
│ │ │ └── password.test.ts # Unit tests
│ │ ├── services/
│ │ │ └── emailService.test.ts
│ │ └── api/
│ │ ├── auth.test.ts # Integration tests
│ │ └── users.test.ts
│ └── ...
├── jest.config.js
└── package.json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage --maxWorkers=2"
}
}
$ npm run test:coverage
--------------------------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
--------------------------|---------|----------|---------|---------|
All files | 92.5 | 88.3 | 95.2 | 92.8 |
auth/ | 95.0 | 90.0 | 100.0 | 95.0 |
middleware.ts | 95.0 | 90.0 | 100.0 | 95.0 |
routes.ts | 95.0 | 90.0 | 100.0 | 95.0 |
utils/ | 90.0 | 85.0 | 90.0 | 90.0 |
password.ts | 90.0 | 85.0 | 90.0 | 90.0 |
--------------------------|---------|----------|---------|---------|
Rules and prohibitions that must be strictly followed.
Test isolation : Each test must be runnable independently
Clear test names : The name must convey what the test verifies
AAA pattern : Arrange (setup) - Act (execute) - Assert (verify) structure
No production DB : Tests must use a separate or in-memory DB
No real external API calls : Mock all external services
No Sleep/Timeout abuse : Use fake timers for time-based tests
Situation : Testing a FastAPI REST API
User request :
Test the user API built with FastAPI using pytest.
Final result :
# tests/conftest.py
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.main import app
from app.database import Base, get_db
# In-memory SQLite for tests
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@pytest.fixture(scope="function")
def db_session():
Base.metadata.create_all(bind=engine)
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
Base.metadata.drop_all(bind=engine)
@pytest.fixture(scope="function")
def client(db_session):
def override_get_db():
try:
yield db_session
finally:
db_session.close()
app.dependency_overrides[get_db] = override_get_db
yield TestClient(app)
app.dependency_overrides.clear()
# tests/test_auth.py
def test_register_user_success(client):
response = client.post("/auth/register", json={
"email": "test@example.com",
"username": "testuser",
"password": "Password123!"
})
assert response.status_code == 201
assert "access_token" in response.json()
assert response.json()["user"]["email"] == "test@example.com"
def test_register_duplicate_email(client):
# First user
client.post("/auth/register", json={
"email": "test@example.com",
"username": "user1",
"password": "Password123!"
})
# Duplicate email
response = client.post("/auth/register", json={
"email": "test@example.com",
"username": "user2",
"password": "Password123!"
})
assert response.status_code == 409
assert "already exists" in response.json()["detail"]
def test_login_success(client):
# Register
client.post("/auth/register", json={
"email": "test@example.com",
"username": "testuser",
"password": "Password123!"
})
# Login
response = client.post("/auth/login", json={
"email": "test@example.com",
"password": "Password123!"
})
assert response.status_code == 200
assert "access_token" in response.json()
def test_protected_route_without_token(client):
response = client.get("/auth/me")
assert response.status_code == 401
def test_protected_route_with_token(client):
# Register and get token
register_response = client.post("/auth/register", json={
"email": "test@example.com",
"username": "testuser",
"password": "Password123!"
})
token = register_response.json()["access_token"]
# Access protected route
response = client.get("/auth/me", headers={
"Authorization": f"Bearer {token}"
})
assert response.status_code == 200
assert response.json()["email"] == "test@example.com"
TDD (Test-Driven Development) : Write tests before writing code
Given-When-Then pattern : Write tests in BDD style
it('should return 404 when user not found', async () => { // Given: a non-existent user ID const nonExistentId = 'non-existent-uuid';
// When: attempting to look up that user
const response = await request(app).get(/users/${nonExistentId});
// Then: 404 response expect(response.status).toBe(404); });
Test Fixtures : Reusable test data
const validUser = { email: 'test@example.com', username: 'testuser', password: 'Password123!' };
--maxWorkers optionSymptom : Passes individually but fails when run together
Cause : DB state shared due to missing beforeEach/afterEach
Fix :
beforeEach(async () => {
await db.migrate.rollback();
await db.migrate.latest();
});
Symptom : Process does not exit after tests complete
Cause : DB connections, servers, etc. not cleaned up
Fix :
afterAll(async () => {
await db.destroy();
await server.close();
});
Symptom : "Timeout - Async callback was not invoked"
Cause : Missing async/await or unhandled Promise
Fix :
// Bad
it('should work', () => {
request(app).get('/users'); // Promise not handled
});
// Good
it('should work', async () => {
await request(app).get('/users');
});
#testing #backend #Jest #Pytest #unit-test #integration-test #TDD #API-test
Weekly Installs
11.8K
Repository
GitHub Stars
88
First Seen
Jan 24, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex11.7K
gemini-cli11.6K
opencode11.6K
github-copilot11.6K
cursor11.6K
amp11.6K
Azure Data Explorer (Kusto) 查询技能:KQL数据分析、日志遥测与时间序列处理
114,200 周安装