Skip to main content
โšก Calmops

Integration Testing Strategies

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

  1. Use test database:

    // โœ… Good: Separate test database
    const testDB = process.env.TEST_DATABASE_URL;
    
    // โŒ Bad: Use production database
    const db = process.env.DATABASE_URL;
    
  2. Clean up after tests:

    // โœ… Good: Clean up
    afterEach(async () => {
      await db.clear();
    });
    
    // โŒ Bad: No cleanup
    
  3. 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

Next Steps

Comments