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):
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();
});
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):
// 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):
// 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