Introduction
\nIntegration testing is a critical stage of development that verifies the interaction of your Swift application components with external dependencies: databases, APIs, file systems, and network services. However, traditional approaches to integration testing on macOS/iOS often suffer from the "environment problem" — tests that work on a developer's machine may fail on a CI server due to differences in library versions, network settings, or database configurations.
\nDocker solves this problem radically: you package all dependencies (PostgreSQL, Redis, Mock servers) into isolated containers that run identically on any platform. In this article, we will explore advanced techniques for integration testing Swift with Docker: from setting up a test environment to parallel execution and CI/CD integration.
\n\n1. Preparing the Test Environment with Docker Compose
\nLet's start by creating an isolated environment for tests. Instead of running each service manually, we use docker-compose.yml to declaratively describe all dependencies.
Example docker-compose.yml for a Swift project
\nversion: '3.8'\nservices:\n postgres_test:\n image: postgres:15-alpine\n environment:\n POSTGRES_USER: test_user\n POSTGRES_PASSWORD: test_pass\n POSTGRES_DB: swift_test_db\n ports:\n - "5433:5432" # Use a non-standard port to avoid conflicts with local DB\n tmpfs: /var/lib/postgresql/data # Data in memory — fast cleanup\n\n redis_test:\n image: redis:7-alpine\n ports:\n - "6380:6379"\n\n mock_server:\n build: ./mock-server # Custom mock server on Vapor\n ports:\n - "8081:8080"\n environment:\n LOG_LEVEL: debug\n\nSwift code for dynamic test configuration
\nTo make tests connect to Docker containers rather than local services, use environment variables:
\nimport XCTest\n\nfinal class IntegrationTests: XCTestCase {\n var app: XCTestApplication?\n\n override func setUp() async throws {\n // Get configuration from environment variables (set in CI or docker-compose)\n let dbHost = ProcessInfo.processInfo.environment["DB_HOST"] ?? "localhost"\n let dbPort = Int(ProcessInfo.processInfo.environment["DB_PORT"] ?? "5433") ?? 5433\n let redisHost = ProcessInfo.processInfo.environment["REDIS_HOST"] ?? "localhost"\n let mockHost = ProcessInfo.processInfo.environment["MOCK_HOST"] ?? "http://localhost:8081"\n\n // Configure the test application (e.g., Vapor or Kitura)\n let config = AppConfig(\n database: DatabaseConfig(host: dbHost, port: dbPort, \n username: "test_user", password: "test_pass",\n database: "swift_test_db"),\n redis: RedisConfig(host: redisHost, port: 6380),\n mockServiceURL: mockHost\n )\n\n app = try await XCTestApplication.start(config: config)\n }\n\n override func tearDown() async throws {\n try await app?.shutdown()\n }\n}\n\n2. Advanced Setup: Parallel Execution and Test Isolation
\nOne of the main problems with integration tests is their execution time. Docker allows you to run multiple copies of the test environment in parallel, significantly speeding up the run.
\n\nIsolation Strategy: One Container per Test Class
\nUse docker-compose --project-name to create unique instances:
#!/bin/bash\n# Run tests with a unique project-name for parallel execution\nTEST_SUITE=$1\nPROJECT_NAME="swift_test_${TEST_SUITE}_$(date +%s)"\n\ndocker-compose -p $PROJECT_NAME up -d --force-recreate\nsleep 5 # Wait for container initialization\n\n# Run a specific test class\nxcodebuild test -scheme MyApp -destina