Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Integration Testing with Docker

🔧 HOW-TO | 📚 REFERENCE | Test with real services using Docker

Test with real services using Docker containers.

Why Docker for Testing?

Docker provides:

  • Real service instances (not mocks)
  • Isolated test environment
  • Reproducible results
  • Easy cleanup

Prerequisites

# Ensure Docker is running
docker --version

# Enable testcontainers feature
[dev-dependencies]
chicago-tdd-tools = { version = "1.3", features = ["testcontainers"] }

Docker Compose for Tests

Create docker-compose.test.yml:

version: '3.8'
services:
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: test_user
      POSTGRES_PASSWORD: test_password
      POSTGRES_DB: test_db
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

Run tests:

# Start services
docker-compose -f docker-compose.test.yml up -d

# Run tests
cargo make test-integration

# Stop services
docker-compose -f docker-compose.test.yml down

Testing with Database

#![allow(unused)]
fn main() {
test!(test_with_postgres, {
    // Fixture provides database connection
    let fixture = TestFixture::new()?;

    // Create test user
    fixture.db().create_user("alice", "alice@example.com")?;

    // Query database
    let user = fixture.db().get_user_by_email("alice@example.com")?;
    assert_eq!(user.name, "alice");
});
}

Testing with Redis

#![allow(unused)]
fn main() {
test!(test_with_redis, {
    let fixture = TestFixture::new()?;

    // Use Redis from fixture
    let cache = fixture.redis();

    // Set value
    cache.set("key", "value")?;

    // Get value
    let value = cache.get("key")?;
    assert_eq!(value, "value");
});
}

Complete Integration Test

#![allow(unused)]
fn main() {
test!(complete_integration_test, {
    let fixture = TestFixture::new()?;
    let db = fixture.db();
    let cache = fixture.redis();

    // 1. Create user in database
    let user = db.create_user("alice", "alice@example.com")?;

    // 2. Cache user
    cache.set(&format!("user:{}", user.id), &user.to_json())?;

    // 3. Verify database
    let retrieved = db.get_user(user.id)?;
    assert_eq!(retrieved.email, "alice@example.com");

    // 4. Verify cache
    let cached = cache.get(&format!("user:{}", user.id))?;
    assert!(!cached.is_empty());
});
}

Handling Docker Failures

If Docker is unavailable:

# Skip integration tests
WEAVER_ALLOW_SKIP=1 cargo make test-unit

# Or just run unit tests
cargo test --lib

Performance Optimization

Docker containers have overhead:

  • Slow: 30-60 seconds per test
  • Solution: Batch related tests
#![allow(unused)]
fn main() {
test!(test_db_operations_batch, {
    let fixture = TestFixture::new()?;

    // Test 1: Create
    let user = fixture.db().create_user("alice", "alice@example.com")?;
    assert_ok!(&user);

    // Test 2: Read
    let retrieved = fixture.db().get_user(user.id)?;
    assert_ok!(&retrieved);

    // Test 3: Update
    fixture.db().update_user(user.id, "new_email@example.com")?;
    let updated = fixture.db().get_user(user.id)?;
    assert_eq!(updated.email, "new_email@example.com");

    // Test 4: Delete
    fixture.db().delete_user(user.id)?;
    let result = fixture.db().get_user(user.id);
    assert_err!(&result);

    // One test, multiple operations, one fixture overhead
});
}

CI/CD Pipeline

GitHub Actions Example

name: Integration Tests

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:15-alpine
        env:
          POSTGRES_PASSWORD: password
        options: --health-cmd pg_isready

    steps:
      - uses: actions/checkout@v3
      - uses: actions-rs/toolchain@v1
      - run: cargo make test-integration

Best Practices

Do:

  • Use Docker for real services
  • Batch related tests
  • Use fixtures for isolation
  • Clean up after tests

Don't:

  • Mock Docker services
  • Share containers between tests
  • Run Docker tests in CI for every commit
  • Forget about cleanup

Troubleshooting

"Docker daemon not running"

Start Docker:

# macOS
open /Applications/Docker.app

# Linux
sudo systemctl start docker

# Windows
Start Docker Desktop

"Port already in use"

Check ports:

docker ps  # See running containers
docker stop <container>

Tests Timeout

Increase timeout:

cargo test --lib -- --test-threads=1  # Sequential

Next Steps

See: Best Practices