testing-strategies
Design comprehensive testing strategies for software quality assurance. Use when planning test coverage, implementing test pyramids, or setting up testing infrastructure. Handles unit testing, integration testing, E2E testing, TDD, and testing best practices.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install supercent-io-skills-template-testing-strategies
Repository
Skill path: .agent-skills/testing-strategies
Design comprehensive testing strategies for software quality assurance. Use when planning test coverage, implementing test pyramids, or setting up testing infrastructure. Handles unit testing, integration testing, E2E testing, TDD, and testing best practices.
Open repositoryBest for
Primary workflow: Research & Ops.
Technical facets: Full Stack, Designer, Testing, Integration.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: supercent-io.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install testing-strategies into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/supercent-io/skills-template before adding testing-strategies to shared team environments
- Use testing-strategies for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: testing-strategies
description: Design comprehensive testing strategies for software quality assurance. Use when planning test coverage, implementing test pyramids, or setting up testing infrastructure. Handles unit testing, integration testing, E2E testing, TDD, and testing best practices.
metadata:
tags: testing, test-strategy, TDD, unit-test, integration-test, E2E, test-pyramid
platforms: Claude, ChatGPT, Gemini
---
# Testing Strategies
## When to use this skill
- **New project**: define a testing strategy
- **Quality issues**: bugs happen frequently
- **Before refactoring**: build a safety net
- **CI/CD setup**: automated tests
## Instructions
### Step 1: Understand the Test Pyramid
```
/\
/E2E\ ← few (slow, expensive)
/______\
/ \
/Integration\ ← medium
/____________\
/ \
/ Unit Tests \ ← many (fast, inexpensive)
/________________\
```
**Ratio guide**:
- Unit: 70%
- Integration: 20%
- E2E: 10%
### Step 2: Unit testing strategy
**Given-When-Then pattern**:
```typescript
describe('calculateDiscount', () => {
it('should apply 10% discount for orders over $100', () => {
// Given: setup
const order = { total: 150, customerId: '123' };
// When: perform action
const discount = calculateDiscount(order);
// Then: verify result
expect(discount).toBe(15);
});
it('should not apply discount for orders under $100', () => {
const order = { total: 50, customerId: '123' };
const discount = calculateDiscount(order);
expect(discount).toBe(0);
});
it('should throw error for invalid order', () => {
const order = { total: -10, customerId: '123' };
expect(() => calculateDiscount(order)).toThrow('Invalid order');
});
});
```
**Mocking strategy**:
```typescript
// Mock external dependencies
jest.mock('../services/emailService');
import { sendEmail } from '../services/emailService';
describe('UserService', () => {
it('should send welcome email on registration', async () => {
// Arrange
const mockSendEmail = sendEmail as jest.MockedFunction<typeof sendEmail>;
mockSendEmail.mockResolvedValueOnce(true);
// Act
await userService.register({ email: '[email protected]', password: 'pass' });
// Assert
expect(mockSendEmail).toHaveBeenCalledWith({
to: '[email protected]',
subject: 'Welcome!',
body: expect.any(String)
});
});
});
```
### Step 3: Integration Testing
**API endpoint tests**:
```typescript
describe('POST /api/users', () => {
beforeEach(async () => {
await db.user.deleteMany(); // Clean DB
});
it('should create user with valid data', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: '[email protected]',
username: 'testuser',
password: 'Password123!'
});
expect(response.status).toBe(201);
expect(response.body.user).toMatchObject({
email: '[email protected]',
username: 'testuser'
});
// Verify it was actually saved to the DB
const user = await db.user.findUnique({ where: { email: '[email protected]' } });
expect(user).toBeTruthy();
});
it('should reject duplicate email', async () => {
// Create first user
await request(app)
.post('/api/users')
.send({ email: '[email protected]', username: 'user1', password: 'Pass123!' });
// Attempt duplicate
const response = await request(app)
.post('/api/users')
.send({ email: '[email protected]', username: 'user2', password: 'Pass123!' });
expect(response.status).toBe(409);
});
});
```
### Step 4: E2E Testing (Playwright)
```typescript
import { test, expect } from '@playwright/test';
test.describe('User Registration Flow', () => {
test('should complete full registration process', async ({ page }) => {
// 1. Visit homepage
await page.goto('http://localhost:3000');
// 2. Click Sign Up button
await page.click('text=Sign Up');
// 3. Fill out form
await page.fill('input[name="email"]', '[email protected]');
await page.fill('input[name="username"]', 'testuser');
await page.fill('input[name="password"]', 'Password123!');
// 4. Submit
await page.click('button[type="submit"]');
// 5. Confirm success message
await expect(page.locator('text=Welcome')).toBeVisible();
// 6. Confirm redirect to dashboard
await expect(page).toHaveURL('http://localhost:3000/dashboard');
// 7. Confirm user info is displayed
await expect(page.locator('text=testuser')).toBeVisible();
});
test('should show error for invalid email', async ({ page }) => {
await page.goto('http://localhost:3000/signup');
await page.fill('input[name="email"]', 'invalid-email');
await page.fill('input[name="password"]', 'Password123!');
await page.click('button[type="submit"]');
await expect(page.locator('text=Invalid email')).toBeVisible();
});
});
```
### Step 5: TDD (Test-Driven Development)
**Red-Green-Refactor Cycle**:
```typescript
// 1. RED: write a failing test
describe('isPalindrome', () => {
it('should return true for palindrome', () => {
expect(isPalindrome('racecar')).toBe(true);
});
});
// 2. GREEN: minimal code to pass the test
function isPalindrome(str: string): boolean {
return str === str.split('').reverse().join('');
}
// 3. REFACTOR: improve the code
function isPalindrome(str: string): boolean {
const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
return cleaned === cleaned.split('').reverse().join('');
}
// 4. Additional test cases
it('should ignore case and spaces', () => {
expect(isPalindrome('A man a plan a canal Panama')).toBe(true);
});
it('should return false for non-palindrome', () => {
expect(isPalindrome('hello')).toBe(false);
});
```
## Output format
### Testing strategy document
```markdown
## Testing Strategy
### Coverage Goals
- Unit Tests: 80%
- Integration Tests: 60%
- E2E Tests: Critical user flows
### Test Execution
- Unit: Every commit (local + CI)
- Integration: Every PR
- E2E: Before deployment
### Tools
- Unit: Jest
- Integration: Supertest
- E2E: Playwright
- Coverage: Istanbul/nyc
### CI/CD Integration
- GitHub Actions: Run all tests on PR
- Fail build if coverage < 80%
- E2E tests on staging environment
```
## Constraints
### Required rules (MUST)
1. **Test isolation**: each test is independent
2. **Fast feedback**: unit tests should be fast (<1 min)
3. **Deterministic**: same input → same result
### Prohibited items (MUST NOT)
1. **Test dependencies**: do not let test A depend on test B
2. **Production DB**: do not use a real DB in tests
3. **Sleep/Timeout**: avoid time-based tests
## Best practices
1. **AAA pattern**: Arrange-Act-Assert
2. **Test names**: "should ... when ..."
3. **Edge Cases**: boundary values, null, empty values
4. **Happy Path + Sad Path**: both success/failure scenarios
## References
- [Test Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html)
- [Jest](https://jestjs.io/)
- [Playwright](https://playwright.dev/)
- [Testing Best Practices](https://github.com/goldbergyoni/javascript-testing-best-practices)
## Metadata
### Version
- **Current version**: 1.0.0
- **Last updated**: 2025-01-01
- **Compatible platforms**: Claude, ChatGPT, Gemini
### Related skills
- [backend-testing](../backend-testing/SKILL.md)
- [code-review](../code-review/SKILL.md)
### Tags
`#testing` `#test-strategy` `#TDD` `#unit-test` `#integration-test` `#E2E` `#code-quality`
## Examples
### Example 1: Basic usage
<!-- Add example content here -->
### Example 2: Advanced usage
<!-- Add advanced example content here -->
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### ../backend-testing/SKILL.md
```markdown
---
name: backend-testing
description: Write comprehensive backend tests including unit tests, integration tests, and API tests. Use when testing REST APIs, database operations, authentication flows, or business logic. Handles Jest, Pytest, Mocha, testing strategies, mocking, and test coverage.
metadata:
tags: testing, backend, unit-test, integration-test, API-test, Jest, Pytest, TDD
platforms: Claude, ChatGPT, Gemini
---
# Backend Testing
## When to use this skill
Specific situations that should trigger this skill:
- **New feature development**: Write tests first using TDD (Test-Driven Development)
- **Adding API endpoints**: Test success and failure cases for REST APIs
- **Bug fixes**: Add tests to prevent regressions
- **Before refactoring**: Write tests that guarantee existing behavior
- **CI/CD setup**: Build automated test pipelines
## Input Format
Format and required/optional information to collect from the user:
### Required information
- **Framework**: Express, Django, FastAPI, Spring Boot, etc.
- **Test tool**: Jest, Pytest, Mocha/Chai, JUnit, etc.
- **Test target**: API endpoints, business logic, DB operations, etc.
### Optional information
- **Database**: PostgreSQL, MySQL, MongoDB (default: in-memory DB)
- **Mocking library**: jest.mock, sinon, unittest.mock (default: framework built-in)
- **Coverage target**: 80%, 90%, etc. (default: 80%)
- **E2E tool**: Supertest, TestClient, RestAssured (optional)
### Input example
```
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
```
## Instructions
Step-by-step task order to follow precisely.
### Step 1: Set up the test environment
Install and configure the test framework and tools.
**Tasks**:
- Install test libraries
- Configure test database (in-memory or separate DB)
- Separate environment variables (.env.test)
- Configure jest.config.js or pytest.ini
**Example** (Node.js + Jest + Supertest):
```bash
npm install --save-dev jest ts-jest @types/jest supertest @types/supertest
```
**jest.config.js**:
```javascript
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):
```typescript
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();
});
```
### Step 2: Write Unit Tests (business logic)
Write unit tests for individual functions and classes.
**Tasks**:
- Test pure functions (no dependencies)
- Isolate dependencies via mocking
- Test edge cases (boundary values, exceptions)
- AAA pattern (Arrange-Act-Assert)
**Decision criteria**:
- No external dependencies (DB, API) -> pure Unit Test
- External dependencies present -> use Mock/Stub
- Complex logic -> test various input cases
**Example** (password validation function):
```typescript
// 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);
});
});
```
### Step 3: Integration Test (API endpoints)
Write integration tests for API endpoints.
**Tasks**:
- Test HTTP requests/responses
- Success cases (200, 201)
- Failure cases (400, 401, 404, 500)
- Authentication/authorization tests
- Input validation tests
**Checklist**:
- [x] Verify status code
- [x] Validate response body structure
- [x] Confirm database state changes
- [x] Validate error messages
**Example** (Express.js + Supertest):
```typescript
// 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: '[email protected]',
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('[email protected]');
// Verify the record was actually saved to DB
const user = await db.user.findUnique({ where: { email: '[email protected]' } });
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: '[email protected]',
username: 'user1',
password: 'Password123!'
});
// Second attempt with same email
const response = await request(app)
.post('/api/auth/register')
.send({
email: '[email protected]',
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: '[email protected]',
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: '[email protected]'
// 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: '[email protected]',
username: 'testuser',
password: 'Password123!'
});
});
it('should login with valid credentials', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: '[email protected]',
password: 'Password123!'
});
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('accessToken');
expect(response.body).toHaveProperty('refreshToken');
expect(response.body.user.email).toBe('[email protected]');
});
it('should reject invalid password', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: '[email protected]',
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: '[email protected]',
password: 'Password123!'
});
expect(response.status).toBe(401);
});
});
```
### Step 4: Authentication/Authorization Tests
Test JWT tokens and role-based access control.
**Tasks**:
- Confirm 401 when accessing without a token
- Confirm successful access with a valid token
- Test expired token handling
- Role-based permission tests
**Example**:
```typescript
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: '[email protected]',
username: 'user',
password: 'Password123!'
});
accessToken = userResponse.body.accessToken;
// Admin token
const adminResponse = await request(app)
.post('/api/auth/register')
.send({
email: '[email protected]',
username: 'admin',
password: 'Password123!'
});
// Update role to 'admin' in DB
await db.user.update({
where: { email: '[email protected]' },
data: { role: 'admin' }
});
// Log in again to get a new token
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: '[email protected]',
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('[email protected]');
});
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: '[email protected]' } });
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: '[email protected]' } });
const response = await request(app)
.delete(`/api/users/${targetUser.id}`)
.set('Authorization', `Bearer ${accessToken}`);
expect(response.status).toBe(403);
});
});
});
```
### Step 5: Mocking and Test Isolation
Mock external dependencies to isolate tests.
**Tasks**:
- Mock external APIs
- Mock email sending
- Mock file system
- Mock time-related functions
**Example** (mocking an external API):
```typescript
// 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('[email protected]', '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('[email protected]', 'token123'))
.rejects
.toThrow('Failed to send email');
});
});
```
## Output format
Defines the exact format that outputs must follow.
### Basic structure
```
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
```
### Test run scripts (package.json)
```json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage --maxWorkers=2"
}
}
```
### Coverage report
```bash
$ 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 |
--------------------------|---------|----------|---------|---------|
```
## Constraints
Rules and prohibitions that must be strictly followed.
### Required rules (MUST)
1. **Test isolation**: Each test must be runnable independently
- Reset state with beforeEach/afterEach
- Do not depend on test execution order
2. **Clear test names**: The name must convey what the test verifies
- ✅ 'should reject duplicate email'
- ❌ 'test1'
3. **AAA pattern**: Arrange (setup) - Act (execute) - Assert (verify) structure
- Improves readability
- Clarifies test intent
### Prohibited (MUST NOT)
1. **No production DB**: Tests must use a separate or in-memory DB
- Risk of losing real data
- Cannot isolate tests
2. **No real external API calls**: Mock all external services
- Removes network dependency
- Speeds up tests
- Reduces costs
3. **No Sleep/Timeout abuse**: Use fake timers for time-based tests
- jest.useFakeTimers()
- Prevents test slowdowns
### Security rules
- **No hardcoded secrets**: Never hardcode API keys or passwords in test code
- **Separate environment variables**: Use .env.test file
## Examples
### Example 1: Python FastAPI tests (Pytest)
**Situation**: Testing a FastAPI REST API
**User request**:
```
Test the user API built with FastAPI using pytest.
```
**Final result**:
```python
# 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": "[email protected]",
"username": "testuser",
"password": "Password123!"
})
assert response.status_code == 201
assert "access_token" in response.json()
assert response.json()["user"]["email"] == "[email protected]"
def test_register_duplicate_email(client):
# First user
client.post("/auth/register", json={
"email": "[email protected]",
"username": "user1",
"password": "Password123!"
})
# Duplicate email
response = client.post("/auth/register", json={
"email": "[email protected]",
"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": "[email protected]",
"username": "testuser",
"password": "Password123!"
})
# Login
response = client.post("/auth/login", json={
"email": "[email protected]",
"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": "[email protected]",
"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"] == "[email protected]"
```
## Best practices
### Quality improvements
1. **TDD (Test-Driven Development)**: Write tests before writing code
- Clarifies requirements
- Improves design
- Naturally achieves high coverage
2. **Given-When-Then pattern**: Write tests in BDD style
```typescript
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);
});
```
3. **Test Fixtures**: Reusable test data
```typescript
const validUser = {
email: '[email protected]',
username: 'testuser',
password: 'Password123!'
};
```
### Efficiency improvements
- **Parallel execution**: Speed up tests with Jest's `--maxWorkers` option
- **Snapshot Testing**: Save snapshots of UI components or JSON responses
- **Coverage thresholds**: Enforce minimum coverage in jest.config.js
## Common Issues
### Issue 1: Test failures caused by shared state between tests
**Symptom**: Passes individually but fails when run together
**Cause**: DB state shared due to missing beforeEach/afterEach
**Fix**:
```typescript
beforeEach(async () => {
await db.migrate.rollback();
await db.migrate.latest();
});
```
### Issue 2: "Jest did not exit one second after the test run"
**Symptom**: Process does not exit after tests complete
**Cause**: DB connections, servers, etc. not cleaned up
**Fix**:
```typescript
afterAll(async () => {
await db.destroy();
await server.close();
});
```
### Issue 3: Async test timeout
**Symptom**: "Timeout - Async callback was not invoked"
**Cause**: Missing async/await or unhandled Promise
**Fix**:
```typescript
// Bad
it('should work', () => {
request(app).get('/users'); // Promise not handled
});
// Good
it('should work', async () => {
await request(app).get('/users');
});
```
## References
### Official docs
- [Jest Documentation](https://jestjs.io/docs/getting-started)
- [Pytest Documentation](https://docs.pytest.org/)
- [Supertest GitHub](https://github.com/visionmedia/supertest)
### Learning resources
- [Testing JavaScript with Kent C. Dodds](https://testingjavascript.com/)
- [Test-Driven Development by Example (Kent Beck)](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530)
### Tools
- [Istanbul/nyc](https://istanbul.js.org/) - code coverage
- [nock](https://github.com/nock/nock) - HTTP mocking
- [faker.js](https://fakerjs.dev/) - test data generation
## Metadata
### Version
- **Current version**: 1.0.0
- **Last updated**: 2025-01-01
- **Compatible platforms**: Claude, ChatGPT, Gemini
### Related skills
- [api-design](../api-design/SKILL.md): Design APIs alongside tests
- [authentication-setup](../authentication/SKILL.md): Test authentication systems
### Tags
`#testing` `#backend` `#Jest` `#Pytest` `#unit-test` `#integration-test` `#TDD` `#API-test`
```
### ../code-review/SKILL.md
```markdown
---
name: code-review
description: Conduct thorough, constructive code reviews for quality and security. Use when reviewing pull requests, checking code quality, identifying bugs, or auditing security. Handles best practices, SOLID principles, security vulnerabilities, performance analysis, and testing coverage.
allowed-tools: Read Grep Glob
metadata:
tags: code-review, code-quality, security, best-practices, PR-review
platforms: Claude, ChatGPT, Gemini
---
# Code Review
## When to use this skill
- Reviewing pull requests
- Checking code quality
- Providing feedback on implementations
- Identifying potential bugs
- Suggesting improvements
- Security audits
- Performance analysis
## Instructions
### Step 1: Understand the context
**Read the PR description**:
- What is the goal of this change?
- Which issues does it address?
- Are there any special considerations?
**Check the scope**:
- How many files changed?
- What type of changes? (feature, bugfix, refactor)
- Are tests included?
### Step 2: High-level review
**Architecture and design**:
- Does the approach make sense?
- Is it consistent with existing patterns?
- Are there simpler alternatives?
- Is the code in the right place?
**Code organization**:
- Clear separation of concerns?
- Appropriate abstraction levels?
- Logical file/folder structure?
### Step 3: Detailed code review
**Naming**:
- [ ] Variables: descriptive, meaningful names
- [ ] Functions: verb-based, clear purpose
- [ ] Classes: noun-based, single responsibility
- [ ] Constants: UPPER_CASE for true constants
- [ ] Avoid abbreviations unless widely known
**Functions**:
- [ ] Single responsibility
- [ ] Reasonable length (< 50 lines ideally)
- [ ] Clear inputs and outputs
- [ ] Minimal side effects
- [ ] Proper error handling
**Classes and objects**:
- [ ] Single responsibility principle
- [ ] Open/closed principle
- [ ] Liskov substitution principle
- [ ] Interface segregation
- [ ] Dependency inversion
**Error handling**:
- [ ] All errors caught and handled
- [ ] Meaningful error messages
- [ ] Proper logging
- [ ] No silent failures
- [ ] User-friendly errors for UI
**Code quality**:
- [ ] No code duplication (DRY)
- [ ] No dead code
- [ ] No commented-out code
- [ ] No magic numbers
- [ ] Consistent formatting
### Step 4: Security review
**Input validation**:
- [ ] All user inputs validated
- [ ] Type checking
- [ ] Range checking
- [ ] Format validation
**Authentication & Authorization**:
- [ ] Proper authentication checks
- [ ] Authorization for sensitive operations
- [ ] Session management
- [ ] Password handling (hashing, salting)
**Data protection**:
- [ ] No hardcoded secrets
- [ ] Sensitive data encrypted
- [ ] SQL injection prevention
- [ ] XSS prevention
- [ ] CSRF protection
**Dependencies**:
- [ ] No vulnerable packages
- [ ] Dependencies up-to-date
- [ ] Minimal dependency usage
### Step 5: Performance review
**Algorithms**:
- [ ] Appropriate algorithm choice
- [ ] Reasonable time complexity
- [ ] Reasonable space complexity
- [ ] No unnecessary loops
**Database**:
- [ ] Efficient queries
- [ ] Proper indexing
- [ ] N+1 query prevention
- [ ] Connection pooling
**Caching**:
- [ ] Appropriate caching strategy
- [ ] Cache invalidation handled
- [ ] Memory usage reasonable
**Resource management**:
- [ ] Files properly closed
- [ ] Connections released
- [ ] Memory leaks prevented
### Step 6: Testing review
**Test coverage**:
- [ ] Unit tests for new code
- [ ] Integration tests if needed
- [ ] Edge cases covered
- [ ] Error cases tested
**Test quality**:
- [ ] Tests are readable
- [ ] Tests are maintainable
- [ ] Tests are deterministic
- [ ] No test interdependencies
- [ ] Proper test data setup/teardown
**Test naming**:
```python
# Good
def test_user_creation_with_valid_data_succeeds():
pass
# Bad
def test1():
pass
```
### Step 7: Documentation review
**Code comments**:
- [ ] Complex logic explained
- [ ] No obvious comments
- [ ] TODOs have tickets
- [ ] Comments are accurate
**Function documentation**:
```python
def calculate_total(items: List[Item], tax_rate: float) -> Decimal:
"""
Calculate the total price including tax.
Args:
items: List of items to calculate total for
tax_rate: Tax rate as decimal (e.g., 0.1 for 10%)
Returns:
Total price including tax
Raises:
ValueError: If tax_rate is negative
"""
pass
```
**README/docs**:
- [ ] README updated if needed
- [ ] API docs updated
- [ ] Migration guide if breaking changes
### Step 8: Provide feedback
**Be constructive**:
```
✅ Good:
"Consider extracting this logic into a separate function for better
testability and reusability:
def validate_email(email: str) -> bool:
return '@' in email and '.' in email.split('@')[1]
This would make it easier to test and reuse across the codebase."
❌ Bad:
"This is wrong. Rewrite it."
```
**Be specific**:
```
✅ Good:
"On line 45, this query could cause N+1 problem. Consider using
.select_related('author') to fetch related objects in a single query."
❌ Bad:
"Performance issues here."
```
**Prioritize issues**:
- 🔴 Critical: Security, data loss, major bugs
- 🟡 Important: Performance, maintainability
- 🟢 Nice-to-have: Style, minor improvements
**Acknowledge good work**:
```
"Nice use of the strategy pattern here! This makes it easy to add
new payment methods in the future."
```
## Review checklist
### Functionality
- [ ] Code does what it's supposed to do
- [ ] Edge cases handled
- [ ] Error cases handled
- [ ] No obvious bugs
### Code Quality
- [ ] Clear, descriptive naming
- [ ] Functions are small and focused
- [ ] No code duplication
- [ ] Consistent with codebase style
- [ ] No code smells
### Security
- [ ] Input validation
- [ ] No hardcoded secrets
- [ ] Authentication/authorization
- [ ] No SQL injection vulnerabilities
- [ ] No XSS vulnerabilities
### Performance
- [ ] No obvious bottlenecks
- [ ] Efficient algorithms
- [ ] Proper database queries
- [ ] Resource management
### Testing
- [ ] Tests included
- [ ] Good test coverage
- [ ] Tests are maintainable
- [ ] Edge cases tested
### Documentation
- [ ] Code is self-documenting
- [ ] Comments where needed
- [ ] Docs updated
- [ ] Breaking changes documented
## Common issues
### Anti-patterns
**God class**:
```python
# Bad: One class doing everything
class UserManager:
def create_user(self): pass
def send_email(self): pass
def process_payment(self): pass
def generate_report(self): pass
```
**Magic numbers**:
```python
# Bad
if user.age > 18:
pass
# Good
MINIMUM_AGE = 18
if user.age > MINIMUM_AGE:
pass
```
**Deep nesting**:
```python
# Bad
if condition1:
if condition2:
if condition3:
if condition4:
# deeply nested code
# Good (early returns)
if not condition1:
return
if not condition2:
return
if not condition3:
return
if not condition4:
return
# flat code
```
### Security vulnerabilities
**SQL Injection**:
```python
# Bad
query = f"SELECT * FROM users WHERE id = {user_id}"
# Good
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
```
**XSS**:
```javascript
// Bad
element.innerHTML = userInput;
// Good
element.textContent = userInput;
```
**Hardcoded secrets**:
```python
# Bad
API_KEY = "sk-1234567890abcdef"
# Good
API_KEY = os.environ.get("API_KEY")
```
## Best practices
1. **Review promptly**: Don't make authors wait
2. **Be respectful**: Focus on code, not the person
3. **Explain why**: Don't just say what's wrong
4. **Suggest alternatives**: Show better approaches
5. **Use examples**: Code examples clarify feedback
6. **Pick your battles**: Focus on important issues
7. **Acknowledge good work**: Positive feedback matters
8. **Review your own code first**: Catch obvious issues
9. **Use automated tools**: Let tools catch style issues
10. **Be consistent**: Apply same standards to all code
## Tools to use
**Linters**:
- Python: pylint, flake8, black
- JavaScript: eslint, prettier
- Go: golint, gofmt
- Rust: clippy, rustfmt
**Security**:
- Bandit (Python)
- npm audit (Node.js)
- OWASP Dependency-Check
**Code quality**:
- SonarQube
- CodeClimate
- Codacy
## References
- [Google Code Review Guidelines](https://google.github.io/eng-practices/review/)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [Clean Code by Robert C. Martin](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)
## Examples
### Example 1: Basic usage
<!-- Add example content here -->
### Example 2: Advanced usage
<!-- Add advanced example content here -->
```