#[test_casing]Expand description
Flattens a parameterized test into a collection of test cases.
§Inputs
This attribute must be placed on a freestanding function with 1..8 arguments. The attribute must be invoked with 2 values:
- Number of test cases, a number literal
- A case iterator expression evaluating to an implementation of
IntoIteratorwithDebuggable,'staticitems. If the target function has a single argument, the iterator item type must equal to the argument type. Otherwise, the iterator must return a tuple in which each item corresponds to the argument with the same index.
A case iterator expression may reference the environment (e.g., it can be a name of a constant). It doesn’t need to be a constant expression (e.g., it may allocate in heap). It should return at least the number of items specified as the first attribute argument, and can return more items; these additional items will not be tested.
§Mapping arguments
To support more idiomatic signatures for parameterized test functions, it is possible
to map from the type returned by the case iterator. The only supported kind of mapping
so far is taking a shared reference (i.e., T → &T). The mapping is enabled by placing
the #[map(ref)] attribute on the corresponding argument. Optionally, the reference &T
can be further mapped with a function / method (e.g., &String → &str with
String::as_str()). This is specified as #[map(ref = path::to::method)], a la
serde transforms.
§Examples
§Basic usage
test_casing macro accepts 2 args: number of cases and the iterator expression.
The latter can be any valid Rust expression.
#[test_casing(5, 0..5)]
// #[test] attribute is optional and is added automatically
// provided that the test function is not `async`.
fn number_is_small(number: i32) {
assert!(number < 10);
}Functions returning Results are supported as well.
use std::error::Error;
#[test_casing(3, ["0", "42", "-3"])]
fn parsing_numbers(s: &str) -> Result<(), Box<dyn Error>> {
let number: i32 = s.parse()?;
assert!(number.abs() < 100);
Ok(())
}The function on which the test_casing attribute is placed can be accessed from other code
(e.g., for more tests):
#[test_casing(3, ["0", "42", "-3"])]
fn parsing_numbers(s: &str) -> Result<(), Box<dyn Error>> {
// snipped...
}
#[test]
fn parsing_number_error() {
assert!(parsing_numbers("?").is_err());
}The number of test cases is asserted in a separate generated test. This ensures that no cases are skipped by “underreporting” this count (e.g., if the test cases expression is extended).
§Case expressions
Case expressions can be extracted to a constant for reuse or better code structuring.
const CASES: TestCases<(String, i32)> = cases! {
[0, 42, -3].map(|i| (i.to_string(), i))
};
#[test_casing(3, CASES)]
fn parsing_numbers(s: String, expected: i32) {
let parsed: i32 = s.parse().unwrap();
assert_eq!(parsed, expected);
}This example also shows that semantics of args is up to the writer; some of the args may be expected values, etc.
§Cartesian product
One of possible case expressions is a Product; it can be used to generate test cases
as a Cartesian product of the expressions for separate args.
#[test_casing(6, Product((0_usize..3, ["foo", "bar"])))]
fn numbers_and_strings(number: usize, s: &str) {
assert!(s.len() <= number);
}§Reference args
It is possible to go from a generated argument to its reference by adding
a #[map(ref)] attribute on the argument. The attribute may optionally specify
a path to the transform function from the reference to the desired type
(similar to transform specifications in the serde attr).
const CASES: TestCases<(String, i32)> = cases! {
[0, 42, -3].map(|i| (i.to_string(), i))
};
#[test_casing(3, CASES)]
fn parsing_numbers(#[map(ref)] s: &str, expected: i32) {
// Snipped...
}
#[test_casing(3, CASES)]
fn parsing_numbers_too(
#[map(ref = String::as_str)] s: &str,
expected: i32,
) {
// Snipped...
}§ignore and should_panic attributes
ignore or should_panic attributes can be specified below the test_casing attribute.
They will apply to all generated tests.
#[test_casing(3, ["not", "implemented", "yet"])]
#[ignore = "Promise this will work sometime"]
fn future_test(s: &str) {
unimplemented!()
}
#[test_casing(3, ["not a number", "-", ""])]
#[should_panic(expected = "ParseIntError")]
fn string_conversion_fail(bogus_str: &str) {
bogus_str.parse::<i32>().unwrap();
}§Async tests
test_casing supports all kinds of async test wrappers, such as async_std::test,
tokio::test, actix::test etc. The corresponding attribute just needs to be specified
below the test_casing attribute.
#[test_casing(3, ["0", "42", "-3"])]
#[async_std::test]
async fn parsing_numbers(s: &str) -> Result<(), Box<dyn Error>> {
assert!(s.parse::<i32>()?.abs() < 100);
Ok(())
}