#test #case #tests #unit #testing

macro dev test-case

Provides #[test_case(…)] procedural macro attribute for generating parametrized test cases easily

12 releases (5 stable)

2.0.0-rc1 Dec 19, 2021
1.2.1 Oct 28, 2021
1.2.0 Jun 27, 2021
1.1.0 Jan 10, 2021
0.3.1 Sep 27, 2019

#9 in Testing

Download history 21073/week @ 2021-09-26 21147/week @ 2021-10-03 20144/week @ 2021-10-10 19719/week @ 2021-10-17 23853/week @ 2021-10-24 19300/week @ 2021-10-31 19892/week @ 2021-11-07 19456/week @ 2021-11-14 20414/week @ 2021-11-21 20764/week @ 2021-11-28 24041/week @ 2021-12-05 19756/week @ 2021-12-12 14418/week @ 2021-12-19 12492/week @ 2021-12-26 18753/week @ 2022-01-02 22345/week @ 2022-01-09

68,719 downloads per month
Used in less than 121 crates

MIT license

866 lines

Crates.io Docs.rs MIT License Build Status Maintenance

Test Case


test_case crate provides procedural macro attribute that generates parametrized test instances.

Getting Started

Crate has to be added as a dependency to Cargo.toml:

test-case = "2.0.0-rc1"

and imported to the scope of a block where it's being called (since attribute name collides with rust's built-in custom_test_frameworks) via:

use test_case::test_case;

Example usage:

mod tests {
    use test_case::test_case;

    #[test_case(-2, -4 ; "when both operands are negative")]
    #[test_case(2,  4  ; "when both operands are positive")]
    #[test_case(4,  2  ; "when operands are swapped")]
    fn multiplication_tests(x: i8, y: i8) {
        let actual = (x * y).abs();

        assert_eq!(8, actual)

Output from cargo test for this example:

$ cargo test

running 4 tests
test tests::multiplication_tests::when_both_operands_are_negative ... ok
test tests::multiplication_tests::when_both_operands_are_positive ... ok
test tests::multiplication_tests::when_operands_are_swapped ... ok

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

Advanced use

For #[test_case(body)] the body is built as follows:

body := $arguments ($expected_result)? ($description)?


arguments := $expr(,$expr)*(,)?

Comma separated list of one or more expressions, eg.:

#[test_case(a, b, c,)]
#[test_case(a_method_that_produces_arg(1, 2, 3), "a string")]

Expected result

expected_result := => ($modifier)* $validator

Optional part that provides assertions to instantiated tests.

When using expected_result version of test_case tested function must return a type that can be matched with validator. Each validator description states how to ensure that the type returned by function can be matched.


modifier := ignore | inconclusive

Both ignore and inconclusive keywords indicate that test case should be skipped. This is equivalent to using #[ignore] attribute on normal test. Eg.:

#[test_case(0.0 => ignore 0.0)] // not yet implemented


There are numerous validators provided by test_case:

validator := $simple|$matching|$panicking|$with|$using|$complex


simple := $expr

Accepts any expression that evaluates to function return type and compares it against whatever tested block returns via assert_eq. Eg.:

#[test_case("2.0" => 2.0)]
fn parses_a_string(arg_in: &str) -> f64 {
    body omitted...

matching := matches $pattern

A pattern following keyword matches. Result of a function is compared to pattern via MatchExpression. Eg.:

#[test_case("2.0" => matches Ok(_))]
#[test_case("1.0" => matches Ok(v) if v == 1.0f64)]
#[test_case("abc" => matches Err(_))]

panicking := panics ($expr)?

Indicates that test instance should panic. Works identical to #[should_panic] test attribute. Optional expression after the keyword is treated like expected in should_panic. Eg.:

#[test_case(0 => panics "division by zero")]

with := with $closure

Allows manual assertions of the result of testing function. Closure must indicate argument type and it has to be implicitly convertible from type returned by testing function. Eg.:

#[test_case(2.0 => 0.0)]
#[test_case(0.0 => with |i: f64| assert!(i.is_nan()))]
fn test_division(i: f64) -> f64 {
    0.0 / i

using := using $path

Work similar to with attribute, with the difference being that instead of a closure it accepts path to a function that should validate result of the testing function. Eg.:

fn is_power_of_two(input: u64) {

#[test_case(1 => using self::is_power_of_two)]
fn some_test(input: u64) -> u64 {
    "body omitted..."

complex := (it|is) $complex_expression

complex_expression := $cmp_assertion|$path_assertion|$collection_assertion

cmp_assertion := $ord_assertion|$almost_eq_assertion path_assertion := existing_path|file|dir|directory collection_assertion := contains $expr|contains_in_order $expr ord_assertion := (eq|equal_to|lt|less_than|gt|greater_than|leq|less_or_equal_than|geq|greater_or_equal_than) $expr almost_eq_assertion := (almost_equal_to|almost) $expr precision $expr

Complex assertions are created as an extension to test_case allowing for more flexibility in comparisons. Eg.:

#[test_case(args => is lt 2*3.14)]
fn take_piece_of_circle(...) -> f64 {
    "body omitted..."

#[test_case(args => is existing_path)]
fn installation_created_path(...) -> PathBuf {
    "body omitted..."

#[test_case(args => is almost_equal_to 2.0 precision 0.00001)]
fn some_volatile_computation(...) -> f64 {
    "body omitted..."

#[test_case(args => it contains "Jack")]
fn list_of_users(...) -> Vec<String> {
    "body omitted..."

#[test_case(args => it contains_in_order [1, 2, 3])]
fn sorts_asc(...) -> Vec<i32> {
    "body omitted..."

it and is have equivalent interpretation. Both variants exist in order to make test cases easier to read.

complex assertions are WIP content, use at own discretion.

Notes about async & additional attributes

If test_case is used with async tests, eg. #[tokio::test], or user wants to pass other attributes to each test instance then additional attributes have to be added past first occurrence of #[test_case]. Eg.:

async fn xyz() { }


Licensed under of MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)


Project roadmap is available at link. All contributions are welcome.

Recommended tools:

  • cargo readme - to regenerate README.md based on template and lib.rs comments
  • cargo insta - to review test snapshots
  • cargo edit - to add/remove dependencies
  • cargo fmt - to format code
  • cargo clippy - for all insights and tips
  • cargo fix - for fixing warnings


~17K SLoC