Attribute Macro test_casing::test_casing
source · #[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
IntoIterator
withDebug
gable,'static
items. 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 Result
s 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());
}
§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(())
}