/rustainers

A simple, opinionated way to run containers for tests in your Rust project.

Primary LanguageRust

Build status Crates.io Documentation

rustainers

rustainers is a simple, opinionated way to run containers for tests.

TL;DR

More information about this crate can be found in the crate documentation.

Or just see example directory.

Differences with testcontainers

This crate is an alternative to the testcontainers-rs crate.

The key differences are

For now, the implementation is based on the CLI command. We may add more runner based on Rust api later.

Run a simple container

You need a Runner to launch an image. You can use the Runner::auto function to detect an available runner, or use the Runner::docker, Runner::podman, Runner::nerdctl functions to choose a specific runner.

Then you need to create a runnable image, see module images to use an existing image, or create your own image.

And you just need to start your image. The running container can provide some methods to help you to use this container, e.g. a connection URL to access your container.

use rustainers::runner::{Runner, RunOption};
use rustainers::images::Postgres;

async fn pg() -> anyhow::Result<()> {
    let runner = Runner::auto()?;
    let image = Postgres::default().with_tag("15.2");
    let container = runner.start(image).await?;

    let url = container.url().await?;
    do_something_with_postgres(url).await?;
    Ok(())
}

async fn do_something_with_postgres(url: String) -> anyhow::Result<()> { 
    Ok(())
}

Livecycle of a container

When you start a runnable image, the runner first check the state of the container. It may already exists, or we may need to create it.

Then, the runner wait until all wait strategies to be reached, for example it can check the health of the container.

And it's returning a container. This container have a reference on the runnable image, this image can provide helper functions to retrieve for example the database URL.

When the container is dropped, by default, it stop the container.

You can opt-in to detach the container to avoid stopping in during the drop.

Using compose

To run containers defined with a docker-compose file, you also need a runner.

Then you can start your containers with compose_start.

use rdkafka::producer::{FutureProducer, FutureRecord};
use rdkafka::ClientConfig;
use tracing::{info, Level};

use rustainers::compose::images::KafkaSchemaRegistry;
use rustainers::runner::Runner;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let runner = Runner::auto()?;
    let image = KafkaSchemaRegistry::build_single_kraft().await?;
    let containers = runner.compose_start(image).await?;
    info!("Now I can use {containers}");
    do_something_with_kafka(&containers).await?;

   Ok(())
}

async fn do_something_with_kafka(image: &KafkaSchemaRegistry) -> anyhow::Result<()> {
    let topic = "plop";
    let broker_address = image.broker_address().await?;
    info!("Using kafka broker: {broker_address}");

    let schema_registry_url = image.schema_registry_endpoint().await?;
    info!("Using schema registry: {schema_registry_url}");

    // ...
    Ok(())
}

Create a custom image

See images module documentation.

Create a custom compose images

See compose::images module documentation.