# Best Practices for Testing Microservices with Local Databases in Rust

> Discover best practices for testing microservices with local databases in Rust. Use Docker Compose and environment variables for efficient integration testing. Run tests with `just test` for speed and realism.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: best-practices
- Published: 2026-08-15

---

**Use Docker Compose to spin up PostgreSQL containers locally, configure services via environment variables, and run tests with `just test` for fast, realistic integration testing of the Macro microservices platform.**

The Macro repository is a Rust-based microservices platform that relies on multiple PostgreSQL databases—MacroDB, CommsDB, EmailDB, and ContactsDB—for persistence. Testing these services against real database behavior rather than mocks is essential for catching serialization errors, transaction bugs, and schema mismatches before deployment. This guide covers the workflow used in the `macro-inc/macro` codebase to achieve reliable, fast local testing.

## Start the Local Database Stack with Docker Compose

The foundation of local testing is the [`docker/docker-compose.yml`](https://github.com/macro-inc/macro/blob/main/docker/docker-compose.yml) file at the repository root. This file defines PostgreSQL containers, Redis, networking, ports, and persistence volumes that mirror production conditions.

```bash
docker compose -f docker/docker-compose.yml up -d postgres redis

```

Bringing up containers takes seconds and provides the same driver (`sqlx`) and connection handling code used in production. This eliminates the "works on my machine" gap between development and deployment environments.

## Configure Environment Variables for Database Connections

Services in Macro load database connection strings through the `macro_env_var` crate. The `.env.example` file in the repository root shows the required variables:

- `POSTGRES_URL`
- `REDIS_URL`

For local testing, export these variables pointing to your Docker Compose containers:

```bash
export POSTGRES_URL=postgres://macro:macro@localhost:5432/macro
export REDIS_URL=redis://localhost:6379

```

The `just setup_test_envs` helper can automate this population, loading values appropriate for the local Docker stack.

## Run Tests with the Repository-Wide `just test` Command

The `justfile` at the repository root provides a unified interface for testing. The `test` recipe executes `cargo test -p {service}` for each microservice, running integration tests that communicate with live PostgreSQL containers.

Run tests for a specific service:

```bash
just test -p document_storage_service

```

Run the entire workspace:

```bash
just test

```

Under the hood, the test harness also invokes `just prepare_db` to refresh the SQLx query cache after migrations. This ensures compile-time SQL validation stays synchronized with the actual database schema.

## Keep Database Schema in Sync with Migrations

Whenever you add or modify a migration, you must update the schema and SQLx cache before testing. The migration files live in `crates/macro_db_client/migrations/`.

Run these commands after any schema change:

```bash
just migrate_db    # Apply pending migrations to the local database

just prepare_db    # Update the SQLx offline cache with new query metadata

just test -p {service}

```

The `prepare_db` step populates the `.sqlx` directory, enabling compile-time query validation without a live database during `cargo check`. During actual test execution, the cache is refreshed so live queries match the current schema.

## Isolate Tests by Service with Async Test Modules

Each microservice in Macro resides in its own crate with a dedicated test module. Tests use `#[tokio::test]` attributes to spin up an async runtime and interact with the database concurrently.

Example structure from the codebase:

- Location: [`services/document_storage_service/src/service/document_event_publisher/test.rs`](https://github.com/macro-inc/macro/blob/main/services/document_storage_service/src/service/document_event_publisher/test.rs)

This isolation prevents cross-service pollution while still allowing realistic integration testing against the shared local database instances.

## Validate in CI with Identical Infrastructure

The GitHub Actions workflow at [`.github/workflows/ci.yml`](https://github.com/macro-inc/macro/blob/main/.github/workflows/ci.yml) runs the same Docker Compose stack used locally. This guarantees that every pull request is validated against identical database environments, eliminating environment-specific failures.

The CI pipeline executes:

1. `docker compose -f docker/docker-compose.yml up -d` for services
2. `just migrate_db` and `just prepare_db` for schema synchronization
3. `just test` for the full test suite

## Complete Local Testing Workflow

Here is the minimal command sequence for testing any Macro microservice:

```bash

# 1. Start required containers

docker compose -f docker/docker-compose.yml up -d postgres redis

# 2. Export test environment variables

export POSTGRES_URL=postgres://macro:macro@localhost:5432/macro
export REDIS_URL=redis://localhost:6379

# 3. Synchronize schema and cache

just migrate_db
just prepare_db

# 4. Run service-specific tests

just test -p document_storage_service

```

After adding a new migration, repeat steps 3 and 4 before committing:

```bash
just migrate_db && just prepare_db && just test -p {service}

```

## Summary

- **Use [`docker/docker-compose.yml`](https://github.com/macro-inc/macro/blob/main/docker/docker-compose.yml)** to provision realistic PostgreSQL and Redis containers locally
- **Load connection strings** via the `macro_env_var` crate with variables shown in `.env.example`
- **Execute tests** through `just test` for consistent, cached, service-isolated runs
- **Synchronize schema** with `just migrate_db` and `just prepare_db` after any migration changes
- **Mirror CI locally**—the same commands in [`.github/workflows/ci.yml`](https://github.com/macro-inc/macro/blob/main/.github/workflows/ci.yml) run on every pull request

## Frequently Asked Questions

### Why use real PostgreSQL containers instead of mocks or in-memory databases?

Real containers exercise the same `sqlx` driver, connection pooling, and transaction behavior used in production. This catches serialization failures, deadlock scenarios, and schema mismatches that in-memory SQLite or mocks cannot replicate, as implemented in `macro-inc/macro`.

### How does SQLx offline mode work with local testing?

The `just prepare_db` command populates the `.sqlx` cache with query metadata from your live database. This enables `cargo check` and builds without database connectivity. During `just test`, the cache is refreshed against the live containers so runtime queries match the current schema, stored in `crates/macro_db_client/migrations/`.

### Can I run tests for only one service without starting all containers?

No. Even single-service tests require the PostgreSQL container for that service's database. However, `just test -p {service}` limits test execution to one crate, keeping feedback fast. The Docker Compose startup completes in seconds, minimizing overhead for targeted testing.

### What happens if I forget to run `prepare_db` after a migration?

Tests may fail at compile time with SQLx query validation errors, or at runtime with schema mismatches. The `just test` recipe automatically invokes `prepare_db`, but manual runs of `cargo test` without the justfile require explicit cache updates via `just prepare_db` to stay synchronized.