# Testing

> Every test with infrastructure of its own

Encore.rs tests are ordinary Rust tests: `#[test]`, `#[tokio::test]`, or `#[encore::test]`. Run them with `encore test`, and each test gets infrastructure of its own: a migrated database no other test writes to, an empty bucket, an empty Redis database, and topics nobody else publishes to. Tests can run in parallel without seeing each other's data, and need no cleanup.

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn adds_a_user() -> Result<(), encore::Error> {
        add_user("ada").await?;
        let row = DB.query_one("SELECT count(*) FROM users", &[]).await?;
        assert_eq!(row.get::<_, i64>(0), 1);
        Ok(())
    }
}
```

## Running tests

`encore test` runs every service's tests, several services at a time. For a Rust service it runs `cargo test` in the service's directory, with the SDK's `local` feature:

```shell
encore test                          # every service
encore test --services users         # some services: an expression like `users | billing`
encore test --concurrency 2          # at most two services at a time
```

Pass a command to run it instead of the services' own. Without `--services` it runs once, in the current directory; with `--services` it runs in each selected service's directory:

```shell
encore test cargo test --features encore-dev/local adds_a_user
encore test --services users cargo test --features encore-dev/local -- --nocapture
```

A command you pass gets no features from Encore, so name `encore-dev/local` yourself, or buckets and Pub/Sub won't work.

Run tests with `encore test`, not plain `cargo test`: the SDK needs the infrastructure `encore test` provides, and a test binary started without it can't initialize the SDK and exits.

## What each test gets

Encore provisions a test's resources the first time the test uses them, and releases them when the test ends.

| Resource | In each test |
|---|---|
| [Database](/docs/rs/primitives/databases) | A database of its own, cloned from one with every migration applied. It holds what the migrations insert, and nothing else. |
| [Bucket](/docs/rs/primitives/object-storage) | An empty bucket of its own. |
| [Redis database](/docs/rs/primitives/redis) | An empty database of its own, with its own keys and channels. |
| [Pub/Sub topic](/docs/rs/primitives/pubsub) | A topic of its own. No subscription runs: see [Testing Pub/Sub](#testing-pubsub). |
| [Secret](/docs/rs/primitives/secrets) | The app's local value, unless the test sets one with [`set_secret`](#secrets-and-config). |
| [Config](/docs/rs/develop/config) | The app's value, unless the test sets one with [`set_config`](#secrets-and-config). |
| [Cron job](/docs/rs/primitives/cron-jobs) | Never scheduled. Call what serves the job's path. |

`connection_string()` on a database returns the connection string of the test's own database, so a client such as sqlx sees the same data the test does.

Under `encore test` no other service runs, so `encore::services::base_url` panics. Test a service's HTTP handlers in-process, by calling them or by sending requests to your router directly.

## Which test a call belongs to

Encore works out which test a call belongs to from the thread it is made on:

- The test's own thread, which the test harness names after the test.
- A thread of the Tokio runtime the test runs in, so `tokio::spawn` and `spawn_blocking` from a `#[tokio::test]` are part of the test.

A thread the test starts itself is neither, and using a resource there is an error. Make the thread part of the test by entering the test's scope on it:

```rust
#[tokio::test]
async fn counts_on_another_thread() -> Result<(), encore::Error> {
    let scope = encore::testing::current()?;
    let thread = std::thread::spawn(move || {
        let _guard = scope.enter();
        tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .unwrap()
            .block_on(DB.query_one("SELECT count(*) FROM users", &[]))
            .map(|row| row.get::<_, i64>(0))
    });
    // Wait for it without blocking the test's own runtime.
    let count = tokio::task::spawn_blocking(move || thread.join().unwrap())
        .await
        .unwrap()?;
    assert_eq!(count, 0);
    Ok(())
}
```

- `encore::testing::current()` returns a `TestScope` for the current test. It is `Clone` and `Send`, and fails outside `encore test`.
- `TestScope::enter()` makes the calling thread part of the test until the returned guard is dropped.
- `TestScope::scope(future)` runs a future as part of the test on whatever thread polls it. Use it for work spawned onto a runtime that several tests share.
- `TestScope::name()` returns the test's name as the harness reports it, such as `tests::adds_a_user`.

A test's infrastructure lives as long as the test, or a `TestScope` for it, does.

## `#[encore::test]`

`#[encore::test]` is `#[tokio::test]` that tells Encore about the test before it runs, instead of leaving Encore to work it out at the first call. Its runtime's threads are part of the test from the start, its infrastructure is released as soon as it returns, and it loads its module's [fixture](#a-modules-fixture).

```rust
#[encore::test]
async fn adds_a_user() -> Result<(), encore::Error> {
    // …
    Ok(())
}

#[encore::test(flavor = "multi_thread", worker_threads = 2)]
async fn handles_concurrent_writes() {
    // …
}
```

It takes the same `flavor` (`"current_thread"`, the default, or `"multi_thread"`) and `worker_threads` as `#[tokio::test]`, and a `fixture` argument. It also works on a test that isn't `async`, and keeps the test's other attributes, such as `#[should_panic]`.

## Testing Pub/Sub

Under `encore test` nothing handles what a test publishes. Read back what the test, and the code it called, published to a topic with `encore::testing::published`, and call a subscription's handler yourself when you want it to run:

```rust
static USER_ADDED: Topic<UserAdded> = Topic::new("user-added");

#[encore::pubsub::register]
static GREET: Subscription<UserAdded> = Subscription::new(&USER_ADDED, "greet", greet);

async fn greet(msg: Message<UserAdded>) -> Result<(), encore::Error> {
    // …
    Ok(())
}

#[tokio::test]
async fn greets_new_users() -> Result<(), encore::Error> {
    add_user("ada").await?;

    let published = encore::testing::published(&USER_ADDED)?;
    assert_eq!(published.len(), 1);
    assert_eq!(published[0].data.name, "ada");

    greet(published[0].clone()).await?;
    Ok(())
}
```

`published` returns the messages in the order they were published, each a `Message` with its `data`, `raw_data`, `id`, `attributes` and, on an ordered topic, `ordering_key`.

## Secrets and config

A test can give a secret or a config a value of its own. No other test sees it, and it is gone when the test ends:

```rust
#[tokio::test]
async fn uses_the_test_key() -> Result<(), encore::Error> {
    encore::testing::set_secret(&API_KEY, "test-key")?;
    assert_eq!(*API_KEY.value()?, "test-key");

    encore::testing::set_config(&CHECKOUT, serde_json::json!({ "max_items": 2 }))?;
    assert_eq!(CHECKOUT.get().max_items, 2);
    Ok(())
}
```

`set_secret` takes the value as bytes or a string. For a `Secret<Json<T>>`, pass the JSON. `set_config` checks the value against the config's schema and reads it as the config's type, and returns an error if it doesn't fit.

## Fixtures

A test starts from freshly provisioned resources: migrated but otherwise empty databases, empty buckets and empty Redis databases. A fixture is a state to start from instead. Its setup is ordinary app code that runs once per test run, and each test that loads the fixture gets its own copy of what the setup left, to change as it likes.

Declare fixtures with `encore::fixture!`, and load one at the start of a test:

```rust
#[cfg(test)]
mod tests {
    use super::*;

    encore::fixture! {
        /// Two users and their roster.
        static TEAM = async {
            add_user("ada").await?;
            add_user("grace").await?;
            UPLOADS.upload("roster.txt", b"ada,grace".to_vec()).await?;
            Ok(())
        };
    }

    #[tokio::test]
    async fn lists_the_team() -> Result<(), encore::Error> {
        TEAM.load().await?;
        assert_eq!(list_users().await?.len(), 2);
        Ok(())
    }
}
```

The setup is an expression producing a future, such as an `async` block or a call to an `async fn`, that returns `Result<(), encore::Error>`.

Rules to know:

- **Load first.** Call `load()` before the test first uses a resource. Once a test has a database, bucket or Redis database, what it started from can't change, and `load()` returns an error.
- **What is saved.** Databases, buckets and Redis databases are saved. A resource the setup didn't touch starts as it would without the fixture. Topics, secrets and config start each test as usual.
- **When the setup runs.** The first test of a run to load a fixture builds it, and the others wait for it, across threads and test binaries. Encore keeps a fixture between runs while the app's source is unchanged, unless its setup touched a Redis database: such a fixture is built again each run.
- **Names.** `fixture!` names each fixture after its module and itself. Two fixtures of the same name are one fixture.

### Extending a fixture

A fixture declared `static NAME: PARENT` starts its setup from what `PARENT` saved:

```rust
encore::fixture! {
    static TEAM = async {
        add_user("ada").await?;
        add_user("grace").await?;
        Ok(())
    };

    /// `TEAM`, and one more.
    static BIGGER_TEAM: TEAM = async {
        add_user("linus").await?;
        Ok(())
    };
}
```

### A module's fixture

`for mod` makes a fixture the one every `#[encore::test]` in its module, and in the modules inside it, loads before it runs. The innermost module's fixture wins, and a test can choose another, or none:

```rust
mod with_team {
    use super::*;

    encore::fixture! {
        static SEEDED for mod: TEAM = async {
            add_user("barbara").await?;
            Ok(())
        };
    }

    #[encore::test]
    async fn starts_from_seeded() {
        // Three users from TEAM and SEEDED.
    }

    #[encore::test(fixture = TEAM)]
    async fn starts_from_team() {
        // TEAM's two users.
    }

    #[encore::test(fixture = none)]
    async fn starts_empty() {
        // No fixture.
    }
}
```

A plain `#[tokio::test]` in such a module loads nothing by itself. Rather than run it on the wrong data, Encore returns an error at its first use of a resource until it calls `SEEDED.load().await?` or `encore::testing::without_fixture()?`.

### Declaring a fixture by hand

`fixture!` declares a `Fixture`. You can declare one directly, which suits a fixture shared between modules:

```rust
use encore::testing::Fixture;

pub static SEEDED: Fixture = Fixture::new("seeded", || Box::pin(seed()));
pub static INVOICED: Fixture = Fixture::extending(&SEEDED, "invoiced", || Box::pin(invoice()));

async fn seed() -> Result<(), encore::Error> {
    DB.execute("INSERT INTO users (name) VALUES ('ada'), ('grace')", &[]).await?;
    Ok(())
}

async fn invoice() -> Result<(), encore::Error> {
    // …
    Ok(())
}
```

The name identifies the fixture across the app's test binaries.
