[−][src]Crate rstest
This crate will help you to write simpler tests by leveraging a software testing concept called test fixtures. A fixture is something that you can use in your tests to encapsulate a test's dependencies.
The general idea is to have smaller tests that only describe the thing you're testing while you hide the auxiliary utilities your tests make use of somewhere else. For instance, if you have an application that has many tests with users, shopping baskets, and products, you'd have to create a user, a shopping basket, and product every single time in every test which becomes unwieldy quickly. In order to cut down on that repetition, you can instead use fixtures to declare that you need those objects for your function and the fixtures will take care of creating those by themselves. Focus on the important stuff in your tests!
In rstest
a fixture is a function that can return any kind of valid Rust type. This
effectively means that your fixtures are not limited by the kind of data they can return.
A test can consume an arbitrary number of fixtures at the same time.
What
The rstest
crate defines the following procedural macros:
[rstest]
: A normal Rust test that may additionally take fixtures.[rstest_parametrize]
: Like[rstest]
above but with the added ability to also generate new test cases based on input tables.[rstest_matrix]
: Like[rstest]
above but with the added ability to also generate new test cases for every combination of given values.[fixture]
: To mark a function as a fixture.
Why
Very often in Rust we write tests like this
#[test] fn should_process_two_users() { let mut repository = create_repository(); repository.add("Bob", 21); repository.add("Alice", 22); let processor = string_processor(); processor.send_all(&repository, "Good Morning"); assert_eq!(2, processor.output.find("Good Morning").count()); assert!(processor.output.contains("Bob")); assert!(processor.output.contains("Alice")); }
By making use of [rstest]
we can isolate the dependencies empty_repository
and
string_processor
by passing them as fixtures:
#[rstest] fn should_process_two_users(mut empty_repository: impl Repository, string_processor: FakeProcessor) { empty_repository.add("Bob", 21); empty_repository.add("Alice", 22); string_processor.send_all("Good Morning"); assert_eq!(2, string_processor.output.find("Good Morning").count()); assert!(string_processor.output.contains("Bob")); assert!(string_processor.output.contains("Alice")); }
... or if you use "Alice"
and "Bob"
in other tests, you can isolate alice_and_bob
fixture
and use it directly:
#[fixture] fn alice_and_bob(mut empty_repository: impl Repository) -> impl Repository { empty_repository.add("Bob", 21); empty_repository.add("Alice", 22); empty_repository } #[rstest] fn should_process_two_users(alice_and_bob: impl Repository, string_processor: FakeProcessor) { string_processor.send_all("Good Morning"); assert_eq!(2, string_processor.output.find("Good Morning").count()); assert!(string_processor.output.contains("Bob")); assert!(string_processor.output.contains("Alice")); }
Injecting fixtures as function arguments
rstest
functions can receive fixtures by using them as an input argument. A function decorated
with [rstest]
will resolve each argument name by call the fixture
function. Fixtures should be annotated with the [fixture]
attribute.
Fixtures will be resolved like function calls by following the standard resolution rules. Therefore, an identically named fixture can be use in different context.
mod empty_cases { use super::*; #[fixture] fn repository() -> impl Repository { DataSet::default() } #[rstest] fn should_do_nothing(repository: impl Repository) { //.. test impl .. } } mod non_trivial_case { use super::*; #[fixture] fn repository() -> impl Repository { let mut ds = DataSet::default(); // Fill your dataset with interesting case ds } #[rstest] fn should_notify_all_entries(repository: impl Repository) { //.. test impl .. } }
Last but not least, fixtures can be injected like we saw in alice_and_bob
example.
Creating parametrized tests
You can use use [rstest_parametrize]
to create simple
table-based tests. Let's see the classic Fibonacci exmple:
use rstest::rstest_parametrize; #[rstest_parametrize(input, expected, case(0, 0), case(1, 1), case(2, 1), case(3, 2), case(4, 3), case(5, 5), case(6, 8) )] 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) } }
This will generate a bunch of tests, one for every case()
.
Attribute Macros
fixture | Define a fixture that you can use in all |
rstest | Write a test that can be injected with |
rstest_matrix | Write matrix-based tests: you must indicate arguments and values list that you want to test and
|
rstest_parametrize | Write table-based tests: you must indicate the arguments that you want use in your cases and provide them for each case you want to test. |