teststack

Crates.ioteststack
lib.rsteststack
version0.1.0
created_at2025-06-20 14:08:53.422695+00
updated_at2025-06-20 14:08:53.422695+00
descriptionTest utilities to run testcontainers
homepage
repositoryhttps://github.com/luca-iachini/teststack
max_upload_size
id1719693
size107,303
Luca Iachini (luca-iachini)

documentation

README

Teststack

Teststack is a Rust utility crate that simplifies the setup and management of reusable test containers using the testcontainers library. It starts each container once per test suite and shares it across tests, reducing overhead and complexity.

Features

  • Singleton containers per image type across tests
  • Automatic cleanup at the end of the test run (or on Ctrl+C)
  • Pluggable container support: PostgreSQL, MySQL, and custom containers
  • Easy integration with test frameworks (sqlx::test, rstest)

Getting Started

Add teststack to your Cargo.toml:

[dev-dependencies]
teststack = { version = "0.1", features = ["postgres"] }

Example: Shared Container Across Tests

In the example below, both tests share the same Postgres container instance. This reduces startup overhead and speeds up test execution. The container is automatically shut down at the end of the test harness.

use teststack::stack;

#[stack(postgres(random_db_name))]
#[sqlx::test]
async fn test(pool: PgPool) {
    sqlx::query("SELECT 1")
        .fetch_one(&pool)
        .await
        .expect("failed to execute query");
}

Custom Containers and Configuration

The following example demonstrates how to run a RabbitMq container using teststack. It shows how to customize an existing testcontainers_module image with the ImageExt trait for advanced configuration.

use testcontainers_modules::rabbitmq::RabbitMq;
use testcontainers_modules::testcontainers::{ContainerRequest, ImageExt};
use teststack::DbContainer;
use teststack::{ContainerPort, CustomContainer, stack};

#[stack(container(rabbit()))]
#[tokio::test]
async fn test(rabbit: RabbitConnection) {
    rabbit
        .create_channel()
        .await
        .expect("failed to create channel");
}

fn rabbit() -> ContainerRequest<RabbitMq> {
    RabbitMq::default().with_tag("3.11.0-alpine")
}

struct RabbitConnection(lapin::Connection);

impl std::ops::Deref for RabbitConnection {
    type Target = lapin::Connection;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl teststack::Init<RabbitConnection> for CustomContainer {
    async fn init(self) -> RabbitConnection {
        let port = self
            .get_host_port_ipv4(ContainerPort::Tcp(5672))
            .await
            .unwrap();

        let url = format!("amqp://guest:guest@localhost:{port}");
        let conn = lapin::Connection::connect(&url, lapin::ConnectionProperties::default())
            .await
            .unwrap();
        RabbitConnection(conn)
    }
}

If you don't need any custom configuration, you can simplify the container setup by returning the image directly:

use testcontainers_modules::rabbitmq::RabbitMq;
use teststack::{CustomContainer, stack};

#[stack(container(RabbitMq::default()))]
#[tokio::test]
async fn test(rabbit: CustomContainer) {
    // test
}

Design Overview

All containers are shared per image type. Cleanup is performed once, when the test process exits or receives a Ctrl+C signal.

Cleanup Strategy

Test containers are gracefully cleaned up on process exit using ctor and dtor, with support for both async and blocking cleanup.

Features

  • postgres – enable PostgreSQL test container support
  • mysql – enable MySQL test container support
Commit count: 0

cargo fmt