Crates.io Crates.io 1.49+ Docs.rs MIT License Build Status Maintenance

Test Case

Overview

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:

toml [dev-dependencies] 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:

rust use test_case::test_case;

Example usage:

```rust

[cfg(test)]

mod tests { use testcase::testcase;

#[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:

```sh $ cargo test

running 4 tests test tests::multiplicationtests::whenbothoperandsarenegative ... ok test tests::multiplicationtests::whenbothoperandsarepositive ... ok test tests::multiplicationtests::whenoperandsareswapped ... 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

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

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

[test_case(a, b, c,)]

[test_case(())]

[testcase(amethodthatproduces_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.

Modifiers

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.:

```rust

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

```

Validator

There are numerous validators provided by test_case:

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

Simple

simple := $expr

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

```rust

[test_case("2.0" => 2.0)]

fn parsesastring(arg_in: &str) -> f64 { body omitted... } ```

Matching

matching := matches $pattern

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

```rust

[testcase("2.0" => matches Ok())]

[test_case("1.0" => matches Ok(v) if v == 1.0f64)]

[testcase("abc" => matches Err())]

```

Panicking

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.:

```rust

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

```

With

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.:

```rust

[test_case(2.0 => 0.0)]

[testcase(0.0 => with |i: f64| assert!(i.isnan()))]

fn test_division(i: f64) -> f64 { 0.0 / i } ```

Using

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.:

```rust fn ispoweroftwo(input: u64) { assert!(input.ispoweroftwo()) }

[testcase(1 => using self::ispoweroftwo)]

fn some_test(input: u64) -> u64 { "body omitted..." } ```

Complex

complex := (it|is) $complex_expression

complex_expression := not $complex_expression_inner | $complex_expression_inner (and $complex_expression_inner)* | $complex_expression_inner (or $complex_expression_inner)*

complex_expression_inner := $cmp_assertion|$path_assertion|$collection_assertion|\($complex_expression\)

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.:

```rust

[test_case(args => is lt 2*3.14)]

fn takepieceof_circle(...) -> f64 { "body omitted..." }

[testcase(args => is existingpath)]

fn installationcreatedpath(...) -> PathBuf { "body omitted..." }

[testcase(args => is almostequal_to 2.0 precision 0.00001)]

fn somevolatilecomputation(...) -> f64 { "body omitted..." }

[test_case(args => it contains "Jack")]

fn listofusers(...) -> Vec { "body omitted..." }

[testcase(args => it containsin_order [1, 2, 3])]

fn sorts_asc(...) -> Vec { "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.:

```rust

[test_case(...)]

[tokio::test]

[allow(clippy::noncamelcase_types)]

async fn xyz() { } ```

License

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

Contributing

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