Integration Testing Rust with Docker: Advanced Level

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction to Integration Testing Rust with Docker

\n

Integration testing is a key stage in developing reliable server applications in Rust. Unlike unit tests, integration tests verify the interaction of components with real dependencies: databases, caches, external APIs. Using Docker allows you to isolate the test environment and guarantee reproducibility. In this article, we will cover advanced techniques: parallel container startup, state management, mock services, and working with asynchrony via Tokio.

\n\n

Setting up the Test Environment: Docker and testcontainers

\n

For integration tests in Rust, it is convenient to use the testcontainers crate. It allows you to programmatically start Docker containers, wait for them to be ready, and properly clean up resources. Let's start with the basic setup.

\n

Installing Dependencies

\n

Add to Cargo.toml:

\n
[dev-dependencies]\ntestcontainers = "0.21"\ntokio = { version = "1", features = ["full"] }\nreqwest = { version = "0.12", features = ["json"] }\nserde = { version = "1", features = ["derive"] }\nserde_json = "1"
\n

Basic Example: PostgreSQL Container

\n

Let's create a test that starts PostgreSQL, performs a migration, and checks data insertion:

\n
use testcontainers::{clients, images::postgres::Postgres, Container};\nuse tokio_postgres::{NoTls, Client};\n\nasync fn setup_db() -> (Client, Container<'static, Postgres>) {\n    let docker = clients::Cli::default();\n    let pg_image = Postgres::default();\n    let container = docker.run(pg_image);\n    let host = container.get_host();\n    let port = container.get_host_port_ipv4(5432);\n    let connection_string = format!(\n        "host={host} port={port} user=postgres dbname=postgres",\n    );\n    let (client, connection) = tokio_postgres::connect(&connection_string, NoTls)\n        .await\n        .expect("Failed to connect");\n    tokio::spawn(async move {\n        if let Err(e) = connection.await {\n            eprintln!("Connection error: {e}");\n        }\n    });\n    (client, container)\n}\n\n#[tokio::test]\nasync fn test_insert_user() {\n    let (client, _container) = setup_db().await;\n    client\n        .simple_query("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT)")\n        .await\n        .unwrap();\n    client\n        .execute("INSERT INTO users (name) VALUES ($1)", &[&"Alice"])\n        .await\n        .unwrap();\n    let rows = client.query("SELECT name FROM users", &[]).await.unwrap();\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0].get::<_, String>(0), "Alice");\n}
\n

The container will be automatically removed after the test completes thanks to the Container destructor. This eliminates manual cleanup.

\n\n

Parallel Test Execution and Resource Management

\n

By default, Rust runs tests in parallel threads. If each test starts its own container, this can lead to port conflicts and Docker overload. Let's consider two approaches.

\n

Using Different Ports via Dynamic Allocation

\n

The testcontainers crate automatically assigns free ports on the host. This solves the collision problem but increases resource consumption. For heavy containers (e.g., databases), it's better to use a shared instance:

\n
use once_cell::sync::Lazy;\nuse testcontainers::clients;\n\nstatic DOCKER: Lazy<clients::Cli> = Lazy::new(|| clients::Cli::default());\n\nasync fn get_shared_postgres() -> (tokio_postgres::Client, Container<'static, Postgres>) {\n    let pg_image = Postgres::default();\n    let container = DOCKER.run(pg_image);\n    // ... connection as in the previous example\n    (client, container)\n}
\n

However, this approach is risky: tests can affect each other through shared state. It is recommended to use separate databases within a single container or reset the state

Blogs

Book Recommendations