Skip to content

Frequently Asked Questions

General

What is tanu?

tanu is an async-friendly framework for end-to-end testing of HTTP, gRPC, and GraphQL APIs in Rust. Tests are plain async functions annotated with #[tanu::test]; tanu discovers them at compile time and runs them concurrently from a CLI or an interactive TUI.

How is tanu different from #[test] with tokio and reqwest?

You can write API tests with the standard test harness, but you end up building the surrounding infrastructure yourself. tanu provides:

  • Test discovery with parameterized, serial, and ordered tests
  • An HTTP client that captures every request and response, with credentials masked
  • Assertion macros that report into the runner
  • Projects for running the same suite against multiple environments
  • Test-level retries, fail-fast, and filters
  • A TUI for browsing results and payloads
  • Pluggable reporters, including Allure

Is tanu stable?

tanu is actively developed and used to test real APIs. It is still pre-1.0, so minor releases may contain breaking changes; check the release notes when upgrading.

Installation & Setup

How do I install tanu?

tanu tests live in a binary crate:

cargo new my-api-tests
cd my-api-tests
cargo add tanu
cargo add tokio --features full

Then follow Getting Started to set up main.

Can I add tanu to an existing project?

Yes. The usual approach is a dedicated binary crate in your workspace (for example api-tests/) so that test dependencies stay out of your service. Your tests can depend on your service's crates to reuse request and response types.

Which feature flags do I need?

Feature Needed for
json RequestBuilder::json (sending JSON bodies)
cookies Response::cookies
grpc gRPC testing
graphql GraphQL testing
rustls-tls-webpki-roots / rustls-tls-native-roots Using rustls instead of native TLS (with default-features = false)
tanu = { version = "0.22", features = ["json", "cookies"] }

If you get a "no method named json" error on a request builder, the json feature is missing.

Writing Tests

How do I write a basic test?

use tanu::{check, eyre, http::Client};

#[tanu::test]
async fn my_test() -> eyre::Result<()> {
    let client = Client::new();
    let response = client.get("https://api.example.com").send().await?;
    check!(response.status().is_success());
    Ok(())
}

Can I use parameterized tests?

Yes. Add one #[tanu::test(...)] attribute per case:

#[tanu::test(200)]
#[tanu::test(404)]
#[tanu::test(500)]
async fn status_codes(status: u16) -> eyre::Result<()> {
    Ok(())
}

See Test Attributes for naming rules.

How do I handle authentication?

Use bearer_auth, basic_auth, or a header, and keep the secret in an environment variable:

let token = tanu::get_config().get_str("api_token")?.to_string(); // from TANU_API_TOKEN
let response = client
    .get("https://api.example.com/protected")
    .bearer_auth(token)
    .send()
    .await?;

Credentials in headers, query parameters, and JSON/form bodies are masked in HTTP logs by default.

What assertion macros are available?

Macro Checks
check!(cond) A boolean condition
check_eq!(left, right) Equality, with a colored diff
check_ne!(left, right) Inequality
check_str_eq!(left, right) String equality, with a line-by-line diff

All accept an optional format string and arguments. See Assertions.

Can tests run in a specific order?

Yes. Use #[tanu::test(serial)] or #[tanu::test(serial = "group")] to stop tests from overlapping, and #[tanu::test(ordered)] on a module to run its tests in source order. See Ordered Execution.

HTTP Features

What HTTP methods are supported?

GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS.

How do I send JSON?

Enable the json feature:

let response = client
    .post("https://api.example.com/users")
    .json(&serde_json::json!({ "name": "Alice" }))
    .send()
    .await?;

Reading JSON with response.json::<T>() works without the feature.

How do I send other content types?

Use form for URL-encoded bodies, or body with an explicit content type:

let response = client
    .post("https://api.example.com/data")
    .header("content-type", "application/xml")
    .body(xml_data)
    .send()
    .await?;

Does tanu support cookies?

Yes, with the cookies feature:

for cookie in response.cookies() {
    println!("{}={}", cookie.name(), cookie.value());
}

How do I set a request timeout?

let response = client
    .get("https://api.example.com/slow")
    .timeout(std::time::Duration::from_secs(30))
    .send()
    .await?;

Configuration

How do I configure different environments?

Define one project per environment in tanu.toml:

[[projects]]
name = "staging"
base_url = "https://staging.api.example.com"

[[projects]]
name = "production"
base_url = "https://api.example.com"

Every test runs once per project. Read values with tanu::get_config().get_str("base_url"), and select projects with -p. See Configuration.

Can I skip specific tests in a project?

Yes, with test_ignore. Use full test names, including the crate name:

[[projects]]
name = "production"
test_ignore = ["my_api_tests::users::delete_account"]

Can I run only specific tests in a project?

Yes, with test_only. An empty list runs all tests:

[[projects]]
name = "production"
test_only = ["my_api_tests::health::health_check", "my_api_tests::auth::login"]

How do I configure retries?

Retries re-run a failed test with exponential backoff:

[[projects]]
name = "default"
retry.count = 3
retry.factor = 2.0
retry.jitter = true

See Retry.

Running Tests

How do I run tests?

cargo run -- test                               # run all tests
cargo run -- test -p staging                    # one project
cargo run -- test -m my_api_tests::users        # one module
cargo run -- test -t my_api_tests::users::login # one test
cargo run -- ls                                 # list test names

Module and test filters match full names exactly; they are not patterns. See Command Line Options.

Can I run tests in parallel?

Tests run in parallel by default, with no limit in CLI mode. Limit concurrency with -c 4 or runner.concurrency = 4 in tanu.toml.

How do I use the TUI?

cargo run -- tui

See TUI for key bindings.

Troubleshooting

I see a panic: "cannot access a task-local storage value without setting it first"

This happens when you spawn a task (e.g. tokio::spawn, JoinSet::spawn) from a #[tanu::test] and the spawned task calls tanu::get_config() or a check! macro.

Tokio task-locals are not propagated into spawned tasks. Wrap the future with tanu::scope_current(...):

#[tanu::test]
async fn spawned_task_uses_tanu_apis() -> eyre::Result<()> {
    let handle = tokio::spawn(tanu::scope_current(async move {
        tanu::check!(true);
        let _cfg = tanu::get_config();
        eyre::Ok(())
    }));
    handle.await??;
    Ok(())
}

get_int returns "value not found" for a number in tanu.toml

The typed accessors parse string values. Quote the value (timeout = "5000") or read the raw TOML value with get. See User-defined settings.

My test is listed but doesn't run with -t or test_ignore

Test names include the crate name, e.g. my_api_tests::users::login rather than users::login. Run cargo run -- ls and copy the name from there.

Tests pass individually but fail together

Common causes:

  • Shared state between tests — use serial groups
  • API rate limiting — lower concurrency with -c
  • Tests depending on data created by other tests — use ordered execution or make them independent
  • Expired authentication tokens

How do I debug HTTP requests?

By default, captured HTTP logs are printed for failed tests. To print them for every test:

cargo run -- test --capture-http

Or set it in tanu.toml:

[runner]
capture_http = "all"

Large bodies are truncated at 16KB; raise the limit with --max-body-size. Use --show-sensitive locally if you need to see masked values. The TUI shows the same information interactively.

Integration

Can I use tanu in CI?

Yes. Use the CLI mode; the process exits with a non-zero status when tests fail:

cargo run -- test --color always --fail-fast

Can I generate test reports?

Yes. Use tanu-allure for Allure reports, or implement the Reporter trait for your own format.

Contributing

How can I contribute?

  • Report bugs and request features in GitHub issues
  • Submit pull requests
  • Improve documentation and examples

Where can I get help?

Search the existing issues or open a new one.