tanu¶
High-performance, async-friendly and ergonomic WebAPI testing framework for Rust.
cargo add tanu

tanu is a framework for writing end-to-end tests against HTTP, gRPC, and GraphQL APIs in plain Rust. Tests are ordinary async functions; tanu discovers them at compile time, runs them concurrently, and gives you a CLI and an interactive TUI to run and inspect them.
Features¶
-
Plain async Rust
Annotate an
async fnwith#[tanu::test]. No harness boilerplate, and your tests can reuse your service's types. -
Parameterized tests
Stack
#[tanu::test(args...)]attributes to generate one named test case per input. -
Concurrent by default
Tests run in parallel, with
serialgroups andorderedmodules when steps must not overlap. -
Assertions that report
check!,check_eq!,check_ne!, andcheck_str_eq!return errors with colored diffs instead of panicking. -
HTTP, gRPC, GraphQL
A built-in HTTP client, call capture for tonic channels, and runtime or type-safe GraphQL queries.
-
Multiple environments
Run the same suite against dev, staging, and production, with per-project settings, retries, and allowlists.
-
Safe request logs
Every call is captured for debugging, with tokens, keys, and passwords masked in headers, query strings, and bodies.
-
CLI and TUI
Run in CI from the CLI, or browse requests, headers, and payloads interactively in the terminal UI.
-
Pluggable reporters
Write your own reporter, or generate Allure reports with tanu-allure.
A taste of tanu¶
use tanu::{check, check_eq, eyre, http::Client};
#[tanu::test]
async fn get_returns_200() -> eyre::Result<()> {
let http = Client::new();
let res = http.get("https://httpbin.org/get").send().await?;
check!(res.status().is_success(), "unexpected status: {}", res.status());
Ok(())
}
// One test case is generated per attribute.
#[tanu::test(200)]
#[tanu::test(404)]
#[tanu::test(500)]
async fn status_codes(expected: u16) -> eyre::Result<()> {
let http = Client::new();
let res = http
.get(format!("https://httpbin.org/status/{expected}"))
.send()
.await?;
check_eq!(expected, res.status().as_u16());
Ok(())
}
#[tanu::main]
#[tokio::main]
async fn main() -> eyre::Result<()> {
let runner = run();
let app = tanu::App::new();
app.run(runner).await?;
Ok(())
}
Screenshots¶



Why tanu?¶
As a long-time backend engineer, I wanted API tests that were fast, type-safe, and written in the same language as the services they test. The tools I tried each fell short:
- Postman is a great tool, but not designed for end-to-end API testing. It needs a GUI, assertions are written in JavaScript, and collections become huge JSON files that are hard to review and maintain.
- Playwright is excellent for web end-to-end testing and supports API testing, but I wanted to write tests in the same language as the API implementation.
- Rust's built-in
#[test]with tokio, test-case, and reqwest works, but lacks the structure needed at scale: multiple environments, request capture, retries, reporting, and a way to browse results.
tanu aims to be a dedicated framework for that job while staying plain Rust.
Community¶
Thanks to everyone who has contributed to tanu ✨
And to our sponsors, yuk1ty 🐶 and 2323-code 🥩, for supporting development. 🙏
tanu is licensed under the Apache License 2.0.