Getting started with Rust Cargo Test
Generate beautiful HTML reports using Allure Report and your Rust tests.
INFO
The main user-facing crate is allure-cargotest. If you are building your own integration for a custom runner or framework, use allure-rust-commons instead. The official allure-rust repository also publishes Allure Reqwest (captures reqwest HTTP calls as Allure HTTP Exchange attachments) and Allure Diesel (records executed Diesel queries as Allure steps).
Setting up
1. Prepare your project
Make sure a recent stable Rust toolchain is installed.
The official
allure-rustworkspace is published for Rust 1.74 and newer and uses the Rust 2021 edition.Open a terminal and go to the project directory. For example:
bashcd /home/user/myprojectInstall Allure Report. The official
allure-rustrepository documents the workflow with the Allure 3 CLI.Add the
allure-cargotestintegration:bashcargo add allure-cargotest --devAnnotate your tests with
#[allure_test]. You can also mark helper functions with#[step]so they appear as separate steps in the report.rustuse allure_cargotest::{allure_test, step}; #[step] fn open_login_page() { // your step implementation } #[allure_test] #[test] fn login_works() { allure.epic("Web interface"); allure.feature("Authentication"); allure.story("Login with username and password"); allure.parameter("browser", "firefox"); open_login_page(); allure.attachment("page.html", "text/html", "<html>...</html>"); }
2. Run tests
Run your tests the same way as usual:
cargo testBy default, allure-cargotest writes the results to target/allure-results.
To use a different directory, set ALLURE_RESULTS_DIR before the test run:
ALLURE_RESULTS_DIR=./allure-results cargo testIf the results directory already exists, the new files are added to the existing ones, so that a future report will be based on all of them.
3. Generate a report
After the test run, generate and open the report with the Allure CLI:
allure generate ./target/allure-results --output ./target/allure-report --clean
allure open ./target/allure-reportIf you changed the results directory with ALLURE_RESULTS_DIR, use that path in the allure generate command.
Writing tests
Rust Cargo Test extends plain cargo test output with richer reporting features. You can use it to:
- add descriptions, owners, links, and other metadata,
- organize tests into behavior-based and suite-based hierarchies,
- split the execution into nested steps,
- describe parameters and attachments,
- write async tests with Tokio,
- run only selected tests through a test plan file.
Add metadata
Inside a function marked with #[allure_test], the macro injects an allure facade that you can use to enrich the test result:
use allure_cargotest::allure_test;
#[allure_test(name = "Login works", id = "AUTH-1")]
#[test]
fn login_works() {
allure.description("This test verifies login with a username and a password.");
allure.owner("John Doe");
allure.tag("smoke");
allure.severity("critical");
allure.issue("AUTH-123", "https://jira.example.com/browse/AUTH-123");
allure.tms("TMS-456", "https://tms.example.com/cases/TMS-456");
}A Rust doc comment on the test function is used as the default markdown description, so you often don't need to call allure.description(...) at all:
use allure_cargotest::allure_test;
/// Verifies login with a username and a password.
#[allure_test]
#[test]
fn login_works() {
// ...
}Calling allure.description(...) in the test body overrides the doc-comment description for that run, and allure.description_html(...) sets an explicit HTML description. Use #[allure_test(doc = false)] to disable the doc-comment description for a single test.
Organize tests
Allure supports both behavior-based and suite-based hierarchies. For example:
use allure_cargotest::allure_test;
#[allure_test]
#[test]
fn login_works() {
allure.epic("Web interface");
allure.feature("Authentication");
allure.story("Login with username and password");
allure.parent_suite("UI tests");
allure.suite("Authentication");
allure.sub_suite("Positive scenarios");
}When you use #[allure_test], allure-cargotest also derives suite labels from the Rust module path automatically. Explicit calls to allure.parent_suite(...), allure.suite(...), or allure.sub_suite(...) override the synthetic labels with the same name.
Divide a test into steps
You can declare reusable step functions with #[step]:
use allure_cargotest::{allure_test, step};
#[step(name = "Open login page")]
fn open_login_page() {
// ...
}
#[step(name = "Submit credentials")]
fn submit_credentials() {
// ...
}
#[allure_test]
#[test]
fn login_works() {
open_login_page();
submit_credentials();
}You can also create steps directly from the runtime API. allure.step(name, || { ... }) wraps a closure as a step and returns its value; allure.enter_step(name) returns a guard that keeps the step open until it is dropped, which is useful when the step spans code that isn't a single closure:
use allure_cargotest::allure_test;
#[allure_test]
#[test]
fn login_works() {
allure.step("Open login page", || {
// ...
});
let _guard = allure.enter_step("Check profile page");
// ...
}Add parameters and attachments
Parameters and attachments are stored in the generated Allure results and rendered in the report:
use allure_cargotest::allure_test;
#[allure_test]
#[test]
fn login_works() {
allure.parameter("browser", "firefox");
allure.parameter("environment", "staging");
allure.attachment(
"request.json",
"application/json",
br#"{"username":"demo","rememberMe":true}"#,
);
}Run async tests with Tokio
Compose #[allure_test] with a runtime-specific test macro such as #[tokio::test]. Add and configure Tokio in your own test crate; allure-cargotest itself does not depend on Tokio.
use allure_cargotest::allure_test;
#[allure_test]
#[tokio::test]
async fn login_works_async() {
allure.feature("Authentication");
tokio::task::yield_now().await;
allure.parameter("runtime", "tokio");
}Allure context is available in the root async test body and in awaited helpers. Independently spawned Tokio tasks do not implicitly inherit the current Allure context.
Both sync and async tests can also return Result<(), E>, ExitCode, or another type that implements std::process::Termination, instead of (). Returned Err values and unsuccessful termination values are reported to Allure before Cargo interprets the result:
use allure_cargotest::allure_test;
#[allure_test]
#[test]
fn login_works() -> Result<(), String> {
allure.feature("Authentication");
if !login("demo") {
return Err("login failed".to_string());
}
Ok(())
}Select tests via a test plan file
allure-cargotest supports the standard Allure test plan mechanism via the ALLURE_TESTPLAN_PATH environment variable.
Create a JSON file such as:
{
"version": "1.0",
"tests": [{ "selector": "auth::tests::login_works" }]
}Then run the tests with:
ALLURE_TESTPLAN_PATH=./testplan.json cargo testEntries with selector match the full Rust test name, including its module path, and work with #[allure_test].
WARNING
Entries with id are matched against an explicit Allure ID (or an @allure.id=... / @allure.id:... tag) that the caller passes to the test-plan lookup explicitly. #[allure_test(id = "...")] does not currently forward its id to that lookup, so id entries have no effect on tests written with the macro — use selector entries for them instead. Integrations that call CargoTestReporter::run_test_with_metadata directly can pass an allure_id, and id entries do match there. See Reference for CargoTestReporter.
Building a custom integration
If you need to integrate Allure with a custom Rust test runner or framework, use allure-rust-commons:
cargo add allure-rust-commonsAt that level, you create a writer, initialize a runtime, start a test case, and stop it when execution ends:
use allure_rust_commons::{AllureRuntime, FileSystemResultsWriter, StartTestCaseParams, Status};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let writer = FileSystemResultsWriter::new("target/allure-results")?;
let runtime = AllureRuntime::new(writer);
let lifecycle = runtime.lifecycle();
lifecycle.start_test_case(
StartTestCaseParams::new("login_works").with_full_name("auth::login_works"),
);
// ... update metadata, add steps, and add attachments ...
lifecycle.stop_test_case(Status::Passed, None);
Ok(())
}See Reference for the main macros and runtime APIs, or Configuration for the supported environment variables.