Setup environment for end-to-end testing

When running end-to-end tests, you’ll need to manage external dependencies like databases, message queues, and other services. We have these two options:

Docker Compose

Using Docker Compose for test dependencies:

# docker-compose.test.yml
version: '3.8'
services:
  test-db:
    image: postgres:14
    environment:
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
      POSTGRES_DB: test_db
    ports:
      - '5432:5432'

Pros:

  • Simple setup with declarative configuration
  • Good for local development environments
  • Can start all dependencies with a single command
  • Easier to debug as containers persist between test runs

Cons:

  • Manual cleanup required
  • State can persist between test runs, leading to flaky tests
  • Harder to manage in CI/CD pipelines
  • Port conflicts can occur when running parallel tests

Testcontainers

Using Testcontainers for programmatic container management:

describe('Users API (e2e)', () => {
  let postgres: PostgreSQLContainer;

  beforeAll(async () => {
    postgres = await new PostgreSQLContainer()
      .withDatabase('test_db')
      .withUsername('test')
      .withPassword('test')
      .start();

    // Configure your app to use the container's connection details
    process.env.DATABASE_URL = postgres.getConnectionUri();
  });

  afterAll(async () => {
    await postgres.stop();
  });
});

Pros:

  • Containers are managed programmatically as part of the test lifecycle
  • Automatic cleanup after tests
  • Better parallel test execution support
  • Works well in CI/CD environments

Cons:

  • Slightly more complex setup
  • May require more resources when running many parallel tests

Recommendation

Consider using Testcontainers when:

  • You need reliable, isolated test environments
  • You’re running tests in CI/CD pipelines
  • You want to avoid test flakiness due to shared state
  • You need to run tests in parallel

Use Docker Compose when:

  • You’re primarily running tests locally
  • You need a persistent development environment
  • You want to manually inspect the state of dependencies
  • You have a simple setup with few dependencies