API Testing Strategies
Testing ensures your API works correctly and continues to work as you make changes. Let's explore different testing strategies.
Types of API Tests
1. Unit Tests
Test individual functions and components in isolation.
// Test a single function
describe('validateEmail', () => {
it('should return true for valid email', () => {
expect(validateEmail('user@example.com')).toBe(true);
});
it('should return false for invalid email', () => {
expect(validateEmail('not-an-email')).toBe(false);
});
});
2. Integration Tests
Test how components work together.
describe('User Service', () => {
it('should create user and send welcome email', async () => {
const user = await userService.create({ email: 'test@example.com' });
expect(user.id).toBeDefined();
expect(emailService.send).toHaveBeenCalledWith(user.email);
});
});
3. API Tests (End-to-End)
Test the actual HTTP endpoints.
describe('GET /api/users', () => {
it('should return list of users', async () => {
const response = await request(app)
.get('/api/users')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(response.body.data).toBeArray();
expect(response.body.meta.total).toBeNumber();
});
});
What to Test
Happy Path
Test that things work when used correctly:
it('should create a user with valid data', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'Alice', email: 'alice@example.com' })
.expect(201);
expect(response.body.id).toBeDefined();
});
Error Cases
Test that errors are handled properly:
it('should return 400 for invalid email', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'Alice', email: 'not-an-email' })
.expect(400);
expect(response.body.error.code).toBe('VALIDATION_ERROR');
});
Edge Cases
Test boundary conditions:
it('should handle maximum page size', async () => {
const response = await request(app)
.get('/api/users?limit=1000')
.expect(200);
expect(response.body.data.length).toBeLessThanOrEqual(100); // Max enforced
});
Exercise: Test Case Generator
Loading JavaScript Exercise...
Test Coverage Goals
| Type | Coverage Goal |
|---|---|
| Happy path | 100% |
| Error responses | 100% of documented errors |
| Authentication | All auth scenarios |
| Validation | All required fields |
| Edge cases | Reasonable coverage |
Best Practices
1. Use Test Fixtures
const validUser = {
name: 'Test User',
email: 'test@example.com'
};
it('should create user', async () => {
await request(app).post('/users').send(validUser).expect(201);
});
2. Clean Up After Tests
afterEach(async () => {
await db.users.deleteMany({ email: /@test.com$/ });
});
3. Test Response Structure
expect(response.body).toMatchObject({
id: expect.any(String),
name: 'Alice',
createdAt: expect.any(String)
});
4. Use Environment Variables
const API_URL = process.env.TEST_API_URL || 'http://localhost:3000';
Summary
Effective API testing includes:
- Unit tests for business logic
- Integration tests for components
- End-to-end API tests
- Coverage of happy paths and errors
- Automated in CI/CD pipeline

