Integration Testing Strategies
Integration testing verifies components work together correctly. This article covers integration testing patterns.
Introduction
Integration testing provides:
- Component interaction verification
- API testing
- Database integration testing
- End-to-end workflow testing
- System reliability
Understanding integration testing helps you:
- Test component interactions
- Verify API contracts
- Test database operations
- Catch integration issues
- Ensure system reliability
API Integration Testing
Testing REST APIs
// โ
Good: Test API endpoints
const request = require('supertest');
const app = require('../app');
describe('User API', () => {
it('GET /api/users returns users', async () => {
const response = await request(app)
.get('/api/users')
.expect(200);
expect(response.body).toBeInstanceOf(Array);
});
it('POST /api/users creates user', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'John', email: '[email protected]' })
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe('John');
});
it('GET /api/users/:id returns user', async () => {
const response = await request(app)
.get('/api/users/1')
.expect(200);
expect(response.body.id).toBe(1);
});
it('PUT /api/users/:id updates user', async () => {
const response = await request(app)
.put('/api/users/1')
.send({ name: 'Jane' })
.expect(200);
expect(response.body.name).toBe('Jane');
});
it('DELETE /api/users/:id deletes user', async () => {
await request(app)
.delete('/api/users/1')
.expect(204);
});
});
// โ
Good: Test error responses
describe('User API Errors', () => {
it('returns 404 for non-existent user', async () => {
const response = await request(app)
.get('/api/users/999')
.expect(404);
expect(response.body).toHaveProperty('error');
});
it('returns 400 for invalid data', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: '' })
.expect(400);
expect(response.body).toHaveProperty('error');
});
});
Database Integration Testing
Testing with Database
// โ
Good: Test database operations
describe('User Repository', () => {
let db;
beforeAll(async () => {
db = await connectTestDB();
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
await db.clear();
});
it('saves user to database', async () => {
const user = await userRepository.create({
name: 'John',
email: '[email protected]'
});
expect(user.id).toBeDefined();
expect(user.name).toBe('John');
});
it('retrieves user from database', async () => {
const created = await userRepository.create({
name: 'John',
email: '[email protected]'
});
const retrieved = await userRepository.findById(created.id);
expect(retrieved.name).toBe('John');
});
it('updates user in database', async () => {
const user = await userRepository.create({
name: 'John',
email: '[email protected]'
});
await userRepository.update(user.id, { name: 'Jane' });
const updated = await userRepository.findById(user.id);
expect(updated.name).toBe('Jane');
});
it('deletes user from database', async () => {
const user = await userRepository.create({
name: 'John',
email: '[email protected]'
});
await userRepository.delete(user.id);
const deleted = await userRepository.findById(user.id);
expect(deleted).toBeNull();
});
});
// โ
Good: Test transactions
describe('Transaction', () => {
it('rolls back on error', async () => {
try {
await db.transaction(async (trx) => {
await trx('users').insert({ name: 'John' });
throw new Error('Test error');
});
} catch (err) {
// Expected
}
const users = await db('users');
expect(users).toHaveLength(0);
});
});
Workflow Integration Testing
Testing Complete Workflows
// โ
Good: Test complete workflow
describe('User Registration Workflow', () => {
it('completes registration flow', async () => {
// 1. Register user
const registerResponse = await request(app)
.post('/api/auth/register')
.send({
name: 'John',
email: '[email protected]',
password: 'password123'
})
.expect(201);
const userId = registerResponse.body.id;
// 2. Verify email
const verifyToken = registerResponse.body.verifyToken;
await request(app)
.post('/api/auth/verify')
.send({ token: verifyToken })
.expect(200);
// 3. Login
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: '[email protected]',
password: 'password123'
})
.expect(200);
const token = loginResponse.body.token;
// 4. Access protected resource
await request(app)
.get('/api/profile')
.set('Authorization', `Bearer ${token}`)
.expect(200);
});
});
// โ
Good: Test error scenarios
describe('User Registration Errors', () => {
it('prevents duplicate email registration', async () => {
// Register first user
await request(app)
.post('/api/auth/register')
.send({
name: 'John',
email: '[email protected]',
password: 'password123'
})
.expect(201);
// Try to register with same email
const response = await request(app)
.post('/api/auth/register')
.send({
name: 'Jane',
email: '[email protected]',
password: 'password456'
})
.expect(400);
expect(response.body.error).toContain('email');
});
});
Contract Testing
API Contract Testing
// โ
Good: Test API contract
describe('User API Contract', () => {
it('returns user with expected schema', async () => {
const response = await request(app)
.get('/api/users/1')
.expect(200);
expect(response.body).toMatchObject({
id: expect.any(Number),
name: expect.any(String),
email: expect.any(String),
createdAt: expect.any(String)
});
});
it('returns users list with expected schema', async () => {
const response = await request(app)
.get('/api/users')
.expect(200);
expect(response.body).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
name: expect.any(String),
email: expect.any(String)
})
])
);
});
});
Best Practices
-
Use test database:
// โ Good: Separate test database const testDB = process.env.TEST_DATABASE_URL; // โ Bad: Use production database const db = process.env.DATABASE_URL; -
Clean up after tests:
// โ Good: Clean up afterEach(async () => { await db.clear(); }); // โ Bad: No cleanup -
Test realistic scenarios:
// โ Good: Realistic scenario it('completes user registration', async () => { // Register, verify, login }); // โ Bad: Isolated tests it('registers user', async () => { // Only registration });
Summary
Integration testing is essential. Key takeaways:
- Test API endpoints
- Test database operations
- Test complete workflows
- Verify contracts
- Use test database
- Clean up after tests
- Test error scenarios
- Test realistic workflows
Related Resources
- Supertest Documentation
- Jest Integration Testing
- API Testing Best Practices
- Contract Testing
- Integration Testing Guide
Next Steps
- Learn about E2E Testing
- Explore Test Coverage
- Study CI/CD
- Practice integration testing
- Build comprehensive test suites
Comments