faraday 0.1.0

Parameterized test case library for Rust ⚡️
Documentation
# faraday ⚡️

A _parameterized testing library_ for Rust. 

**Features**

- **Parameterized Testing:** Specify different inputs to test multiple scenarios with a single test definition.
- **Flexible:** Arguments provided to parameterized test cases are expressions. 
- **Works out of the box:** Works with any Rust version out of the box. No custom test harness necessary.
- **Comprehensive:** Minimalistic doesn't mean it lacks features.    
- **Familiar:** Maintains code readability with familiar Rustic syntax.

---

TODO: Freshly forked from `yare`. Everything below is work-in-progress.

## Table of contents

* [Introduction]#faraday-
* [Examples]#examples-back-to-top
* [Arguments are expressions]#arguments-are-expressions-back-to-top
* [Custom test macro (e.g. tokio::test)]#custom-test-macro-eg-tokiotest-back-to-top
* [Return types]#return-types-back-to-top
* [Function qualifiers]#function-qualifiers-back-to-top
* [Global #[parameterized(...)] import]#globally-importing-parameterized-back-to-top
* [Alternatives]#alternatives-back-to-top
* [License]#license-back-to-top

## Examples <sup>(<a href="#faraday-">back to top</a>)</sup>

**A first example**

```rust
fn add5<T: Into<u32>>(component: T) -> u32 {
    component.into() + 5
}

#[cfg(test)]
mod tests {
    use super::*;
    use faraday::parameterized;

    #[parameterized(
      zero_plus_five = { 0, 5 },
      one_plus_five = { 1, 6 },
      two_plus_five = { 2, 7 },
    )]
    fn test_add5(input: u16, expected: u32) {
        assert_eq!(add5(input), expected);
    }
}
```

**An example with values**

```rust
enum Fruit {
    Apple,
    Bramble(BrambleFruit),
    Pear,
}

trait NameOf {
    fn name_of(&self) -> &str;
}

impl NameOf for Fruit {
    fn name_of(&self) -> &str {
        match self {
            Fruit::Apple => "apple",
            Fruit::Bramble(fruit) => fruit.name_of(),
            Fruit::Pear => "pear",
        }
    }
}

enum BrambleFruit {
    Blackberry,
}

impl NameOf for BrambleFruit {
    fn name_of(&self) -> &str {
        match self {
            BrambleFruit::Blackberry => "blackberry",
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use faraday::parameterized;

    #[parameterized(
      apple = { Fruit::Apple, "apple" },
      pear = { Fruit::Pear, "pear" },
      blackberry = { Fruit::Bramble(BrambleFruit::Blackberry), "blackberry" },
    )]
    fn a_fruity_test(fruit: Fruit, name: &str) {
        assert_eq!(fruit.name_of(), name)
    }
}
```

<br>

## Arguments are expressions <sup>(<a href="#faraday-">back to top</a>)</sup>

While the arguments above were simple values, any expression can be used as argument in a test case.

**Example**

In the example below, we [roll](https://github.com/foresterre/faraday/blob/main/src/tests/dice.rs) the dice 3 times, to generate a seed for later roll_dice function calls.
The first argument `seed1` is a _function call_ to roll_dice. This randomness function is seeded with value `0`.
The second argument `seed2` is a _block expression_. In the expression the roll_dice function is called twice.
The test itself takes the maximum of `seed1` and `seed2`, rolls the die 1000 times, and checks that all values
are valid for a d6 die.

```rust
use std::sync::atomic::{AtomicU32, Ordering};
use faraday::parameterized;

#[parameterized(
  // A complex input for the sake of showing that inputs are expressions...
  seeding_randomness_with_two_dice_rolls = 
  {
    roll_dice(&AtomicU32::new(0)),                              // <- This is an expression (a function call)
    {                                                           // <- This is also an expression (a block expression)
      let from_half = roll_dice( &AtomicU32::new(u32::MAX / 2));
      let from_max = roll_dice( &AtomicU32::new(u32::MAX));
      
      u8::min(from_half, from_max)
    }
  }
)]
fn dicey(seed1: u8, seed2: u8) {
    // Creating a base seed in a complicated way for the sake of it.
    let max = u8::max(seed1, seed2);
    let seed = AtomicU32::new(u32::from(max));

    let out_of_bounds_values = (0..1000)         // roll the dice 1000 times
        .map(|_| roll_dice(&seed))
        .find(|value| !(1..=6).contains(value)); // check that the outputs of the dice are just 1, 2, 3, 4, 5 or 6. 

    assert!(out_of_bounds_values.is_none());
}
```

## Custom test macro (e.g. tokio::test) <sup>(<a href="#faraday-">back to top</a>)</sup>

By default, the code generation step of the `parameterized` attribute will generate test cases marked with a `#[test]`
attribute. For example, the `add5` test from the [examples](#examples-back-to-top) would generate something like:

```rust
#[cfg(test)]
mod tests {
    use super::*;
    use faraday::parameterized;

    // Approximate generated code from add5 example:
    #[cfg(test)]
    mod add5 {
        use super::*;

        #[test]
        fn zero_plus_five() {
            let input: u16 = 0;
            let expected: u32 = 5;
            assert_eq!(add5(input), expected);
        }

        #[test]
        fn one_plus_five() {
            let input: u16 = 1;
            let expected: u32 = 6;
            assert_eq!(add5(input), expected);
        }

        #[test]
        fn two_plus_five() {
            let input: u16 = 2;
            let expected: u32 = 7;
            assert_eq!(add5(input), expected);
        }
    }
}
```

However, sometimes a different test macro is desired. An example is when writing tests for projects which depend on tokio.
For this, you may want to use `#[tokio::test]` (it also requires the test function to also have the `async` qualifier).

In faraday, it is possible to specify a custom test macro. To do so, you may add the `#[test_macro(...)]` attribute _after_
a `#[parameterized]` attribute.

**Custom test macro example: tokio::test**

```rust,ignore
use faraday::parameterized;

#[parameterized(
    zero_wait = { 0, 0 },
    show_paused = { 500, 0 },
)]
#[test_macro(tokio::test(start_paused = true))]
async fn test(wait: u64, time_elapsed: u128) {
    let start = std::time::Instant::now();
    tokio::time::sleep(tokio::time::Duration::from_millis(wait)).await;

    assert_eq!(start.elapsed().as_millis(), time_elapsed);
}
```

Gotchas:

* The `#[test_macro(...)]` must always be specified after a `#[parameterized(...)]` attribute.
* Only one `#[test_macro(...)]` attribute per parameterized test function is allowed.
* While you can rename the parameterized attribute using import aliassing (
  e.g. `use faraday::parameterized as pm`), the `test_macro` attribute cannot be renamed,
  since it's not actually defined as a separate macro.
  Instead, the `parameterized` macro parses this attribute as well.

## Return types <sup>(<a href="#faraday-">back to top</a>)</sup>

faraday supports specifying a return type for a parameterized test function.

Note that the underlying test attribute must also have support for return types.
By default, faraday generates individual test cases decorated with the
familiar [test](https://doc.rust-lang.org/reference/attributes/testing.html#the-test-attribute)
attribute, which is included with any Rust distribution by default.

**Example**

```rust
use faraday::parameterized;

#[parameterized(
  ok = { Ok(0) },
  // err = {  Err("noes!".to_string()) }, <-- enabling this would result in a failed test, since the error code will not be an `ErrorCode::Success`. See the `Termination` trait for more.
)]
fn test(value: Result<u32, String>) -> Result<(), String> {
    let v = value?;

    assert_eq!(v.unwrap(), 0);
}

```

## Function qualifiers <sup>(<a href="#faraday-">back to top</a>)</sup>

faraday supports the following function qualifiers: `const`, `async`, `unsafe` and `extern`.
This is particularly useful if you use `#[parameterized(...)]` with a custom test macro such as `tokio::test`, instead
of the built-in test macro.

**Example**

```rust
use faraday::parameterized;

#[parameterized(
  purple = { & [128, 0, 128] },
  orange = { & [255, 127, 0] },
)]
const extern "C" fn has_reds(streamed_color: &[u8]) {
    assert!(streamed_color.first().is_some());
}
```

## Globally importing parameterized <sup>(<a href="#faraday-">back to top</a>)</sup>

If you prefer not to import this library (with `use faraday::parameterized;`) in every test module, you can put
the following snippet at the top of your crate root:

```rust
#[cfg(test)]
#[macro_use]
extern crate faraday;
```

## Alternatives <sup>(<a href="#faraday-">back to top</a>)</sup>

If faraday is not quite what you're looking for, there are some alternatives:
- [Parameterized]https://github.com/foresterre/parameterized: Battle tested attribute macro's with syntax inspired by JUnit (disclaimer: I authored this one too)
- [Yare]https://github.com/foresterre/yare: Battle tested attribute macro's, with pivotted expression syntax (disclaimer: I authored this one too)
- [Rstest]https://github.com/la10736/rstest
- [Test-case]https://github.com/frondeus/test-case

## License <sup>(<a href="#faraday-">back to top</a>)</sup>

Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.

<br>

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.