[][src]Attribute Macro rstest::rstest

#[rstest]

The attribute that you should use for your tests. Your annotated function's arguments can be injected with [fixture]s, provided by parametrized cases or by value lists.

General Syntax

rstest attribute can be applied to any function and you can costumize its parameters by the follow syntax

rstest(
    arg_1,
    ...,
    arg_n[,]
    [::attribute_1[:: ... [::attribute_k]]]
)

Where:

  • arg_i could be one of the follow
    • ident that match to one of function arguments (see parametrized cases for more details)
    • case[::description](v1, ..., vl) a test case (see parametrized cases for more details)
    • fixture(v1, ..., vl) where fixture is one of function arguments that and v1, ..., vl is a partial list of fixture's arguments (see injecting fixtures] for more details)
    • ident => [v1, ..., vl] where ident is one of function arguments and v1, ..., vl is a list of values for ident (see value lists for more details)
  • attribute_j a test attribute

Function's arguments can be present just once as case identity, fixture or value list.

Your test function can use generics, impl or dyn and like any kind of rust tests:

  • return results
  • marked by #[should_panic] attribute

If the test function is an async function rstest will run all tests as async tests. You can use it just with async-std and you should include attributes in async-std's features.

Injecting Fixtures

The simplest case is write a test that can be injected with [fixture]s. You can just declare all used fixtures by passing them as a function's arguments. This can help your test to be neat and make your dependecy clear.

use rstest::*;

#[fixture]
fn injected() -> i32 { 42 }

#[rstest]
fn the_test(injected: i32) {
    assert_eq!(42, injected)
}

[rstest] proc_macro will desugar it to something that isn't so far from

#[test]
fn the_test() {
    let injected=injected();
    assert_eq!(42, injected)
}

Sometimes is useful to have some parametes in your fixtures but your test would override the fixture's default values in some cases. Like in fixture partial injection you can indicate some fixture's arguments also in rstest.

use rstest::*;

#[fixture(name="Alice", age=22)]
fn user(name: impl AsRef<str>, age: u8) -> User { User(name.as_ref().to_owned(), age) }

#[rstest(user("Bob"))]
fn check_user(user: User) {
    assert_eq("Bob", user.name())
}

Test Parametrized Cases

If you would execute your test for a set of input data cases you can define the arguments to use and the cases list. Let see the classical Fibonacci example. In this case we would give the input value and the expected result for a set of cases to test.

use rstest::rstest;

#[rstest(input, expected,
    case(0, 0),
    case(1, 1),
    case(2, 1),
    case(3, 2),
    case(4, 3),
)]
fn fibonacci_test(input: u32, expected: u32) {
    assert_eq!(expected, fibonacci(input))
}

fn fibonacci(input: u32) -> u32 {
    match input {
        0 => 0,
        1 => 1,
        n => fibonacci(n - 2) + fibonacci(n - 1)
    }
}

rstest will produce a 5 indipendent tests and not just one that check every case. Every test can fail indipendently and cargo test will give follow output:

running 5 tests
test fibonacci_test::case_1 ... ok
test fibonacci_test::case_2 ... ok
test fibonacci_test::case_3 ... ok
test fibonacci_test::case_4 ... ok
test fibonacci_test::case_5 ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

The cases input values can be arbitrary Rust expresions that return the argument type.

use rstest::rstest;
  
fn sum(a: usize, b: usize) -> usize { a + b }

#[rstest(s, len,
    case("foo", 3),
    case(String::from("foo"), 2 + 1),
    case(format!("foo"), sum(2, 1)),
)]
fn test_len(s: impl AsRef<str>, len: usize) {
    assert_eq!(s.as_ref().len(), len);
}

Optional case description

Optionally you can give a description to every case simple by follow case with ::my_case_description where my_case_description should be a a valid Rust ident.

#[rstest(input, expected,
    case::zero_base_case(0, 0),
    case::one_base_case(1, 1),
    case(2, 1),
    case(3, 2),
)]

Outuput will be

running 4 tests
test fibonacci_test::case_1_zero_base_case ... ok
test fibonacci_test::case_2_one_base_case ... ok
test fibonacci_test::case_3 ... ok
test fibonacci_test::case_4 ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Use specific case attributes

Every function's attributes that follow the rstest one will used in tests but you can also define a case's attributes set. This feature can be use to mark just some cases as should_panic and chose to have a fine grain on expected panic messages.

In follow example we run 3 tests where the first pass without any panic, in the second we catch a panic but we don't care about the message and in the third one we also check the panic message.

use rstest::rstest;

#[rstest(
    val,
    case::no_panic(0),
    #[should_panic]
    case::panic(1),
    #[should_panic(expected="expected")]
    case::panic_with_message(2),
)]
fn attribute_per_case(val: i32) {
    match val {
        0 => assert!(true),
        1 => panic!("No catch"),
        2 => panic!("expected"),
        _ => unreachable!(),
    }
}

Output:

running 3 tests
test attribute_per_case::case_1_no_panic ... ok
test attribute_per_case::case_3_panic_with_message ... ok
test attribute_per_case::case_2_panic ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Values Lists

Another useful way to write a test and execute it for some values is to use the values list syntax. This syntax can be usefull both for a plain list and for testing all combination of input arguments.


#[rstest(input => ["Jhon", "alice", "My_Name", "Zigy_2001"])]
fn should_be_valid(input: &str) {
    assert!(is_valid(input))
}

or


#[rstest(
    name => ["J", "A", "A________________________________________21"],
    age => [14, 100], // Maybe more than 100 is an error or joke
)]
fn should_accept_all_corner_cases(name: &str, age: u8) {
    assert!(valid_user(name, age))
}

where cargo test output is

running 6 tests
test should_accept_all_corner_cases::name_1::age_1 ... ok
test should_accept_all_corner_cases::name_3::age_1 ... ok
test should_accept_all_corner_cases::name_3::age_2 ... ok
test should_accept_all_corner_cases::name_2::age_1 ... ok
test should_accept_all_corner_cases::name_2::age_2 ... ok
test should_accept_all_corner_cases::name_1::age_2 ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Use Parametrize definition in more tests

If you need to use a test list for more than one test you can use rstest_reuse crate. With this helper crate you can define a template and use it everywhere.

This example is not tested
use rstest::rstest;
use rstest_reuse::{self, *};

#[template]
#[rstest(a,  b,
    case(2, 2),
    case(4/2, 2),
    )
]
fn two_simple_cases(a: u32, b: u32) {}

#[apply(two_simple_cases)]
fn it_works(a: u32, b: u32) {
    assert!(a == b);
}

See rstest_reuse for more dettails.

Async

rstest provides out of the box async support. Just mark your test function as async and it'll use #[async-std::test] to annotate it. This feature can be really useful to build async parametric tests using a tidy syntax:

use rstest::*;

#[rstest(expected, a, b,
    case(5, 2, 3),
    #[should_panic]
    case(42, 40, 1)
 )]
async fn my_async_test(expected: u32, a: u32, b: u32) {
    assert_eq!(expected, async_sum(a, b).await);
}

Currently, you cannot write async #[fixture] and only async-std is supported out of the box. But if you need to use another runtime that provide it's own test attribute (i.e. tokio::test or actix_rt::test) you can use it in your async test like described in Inject Test Attribute.

To use this feature, you need to enable attributes in the async-std features list in your Cargo.toml:

async-std = { version = "1.5", features = ["attributes"] }

Inject Test Attribute

If you would like to use another test attribute for your test you can simply indicate it in your test function's attributes. For instance if you want to test some async function with use actix_rt::test attribute you can just write:

use rstest::*;
use actix_rt;
use std::future::Future;

#[rstest(a, result,
    case(2, async { 4 }),
    case(21, async { 42 })
)]
#[actix_rt::test]
async fn my_async_test(a: u32, result: impl Future<Output=u32>) {
    assert_eq!(2 * a, result.await);
}

Just the attributes that ends with test (last path segment) can be injected.

Putting all Together

All these features can be used together with a mixture of fixture variables, fixed cases and bunch of values. For instance, you might need two test cases which test for panics, one for a logged in user and one for a guest user.


use rstest::*;

#[fixture]
fn repository() -> InMemoryRepository {
    let mut r = InMemoryRepository::default();
    // fill repository with some data
    r
}

#[fixture]
fn alice() -> User {
    User::logged("Alice", "2001-10-04", "London", "UK")
}

#[rstest(user,
    case::authed_user(alice()), // We can use `fixture` also as standard function
    case::guest(User::Guest),   // We can give a name to every case : `guest` in this case
    query => ["     ", "^%$#@!", "...." ]
)]
#[should_panic(expected = "Invalid query error")] // We whould test a panic
fn should_be_invalid_query_error(repository: impl Repository, user: User, query: &str) {
    repository.find_items(&user, query).unwrap();
}

Attributes

Trace Input Arguments

Sometimes can be very helpful to print all test's input arguments. To do it you can use the trace parameter.

use rstest::*;

#[fixture]
fn injected() -> i32 { 42 }

#[rstest(::trace)]
fn the_test(injected: i32) {
    assert_eq!(42, injected)
}

Will print an output like

Testing started at 14.12 ...
------------ TEST ARGUMENTS ------------
injected = 42
-------------- TEST START --------------


Expected :42
Actual   :43

If you want to trace input arguments but skip some of them that don't implement the Debug trait, you can also use the notrace(list, of, inputs) attribute:

#[rstest(::trace::notrace(xzy, have_no_sense))]
fn the_test(injected: i32, xyz: Xyz, have_no_sense: NoSense) {
    assert_eq!(42, injected)
}