[−][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 followident
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 andv1, ..., vl
is a partial list of fixture's arguments (see injecting fixtures] for more details)ident => [v1, ..., vl]
whereident
is one of function arguments andv1, ..., 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
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] fn name() -> &'static str { "Alice" } #[fixture] fn age() -> u8 { 22 } #[fixture] 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
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
Putting all Together
All these features can be used together: take some fixtures, define some fixed cases and, for each case, tests all combinations of given values. For istance you need to test that given your repository in cases of both logged in or guest user should return an invalid query error.
use rstest::*; #[fixture] fn repository() -> InMemoryRepository { let mut r = InMemoryRepository::default(); // fill repository by some data r } #[fixture] fn alice() -> User { User::logged("Alice", "2001-10-04", "London", "UK") } #[rstest(user, case::logged_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) }