rxpect 0.10.0

Extensible fluent expectations for Rust
Documentation
use crate::borrow::BorrowedOrOwned;
use crate::expectations::predicate::PredicateExpectation;
use crate::projection::{ProjectedExpectations, ProjectedExpectationsBuilder};
use crate::{CheckResult, Expectation, ExpectationBuilder};
use std::fmt::Debug;

/// Extension trait for Option expectations
pub trait OptionExpectations<'e, T>
where
    T: Debug,
{
    /// Expect the Option to be Some
    /// ```
    /// # use rxpect::expect;
    /// # use rxpect::expectations::OptionExpectations;
    ///
    /// let option: Option<i32> = Some(42);
    /// expect(option).to_be_some();
    /// ```
    /// asserts that the Option is Some
    fn to_be_some(self) -> Self;

    /// Expect the Option to be None
    /// ```
    /// # use rxpect::expect;
    /// # use rxpect::expectations::OptionExpectations;
    ///
    /// let option: Option<i32> = None;
    /// expect(option).to_be_none();
    /// ```
    /// asserts that the Option is None
    fn to_be_none(self) -> Self;

    /// Expect the Option to be Some and the Some value to match a predicate
    /// ```
    /// # use rxpect::expect;
    /// # use rxpect::expectations::OptionExpectations;
    ///
    /// let option: Option<i32> = Some(42);
    /// expect(option).to_be_some_matching(|v| *v > 40);
    /// ```
    /// asserts that the Option is Some and the predicate returns true when applied to the Some value
    fn to_be_some_matching<F>(self, predicate: F) -> Self
    where
        F: Fn(&T) -> bool + 'e;
}

pub trait ProjectedOptionExpectations<'e, T>
where
    T: Debug + 'e,
{
    /// Expect the Option to be Some and then chain into further expectations
    /// ```
    /// # use rxpect::expect;
    /// # use rxpect::expectations::{EqualityExpectations, ProjectedOptionExpectations};
    ///
    /// let option: Option<i32> = Some(42);
    /// expect(option).to_be_some_and().to_equal(42);
    /// ```
    /// asserts that the Option is Some and the chained expectations hold for the Some value
    fn to_be_some_and(self) -> ProjectedExpectationsBuilder<'e, Self, Option<T>, T>
    where
        Self: Sized + ExpectationBuilder<'e, Value = Option<T>>;
}

fn some_extract<T: Debug>(option: &Option<T>) -> Option<BorrowedOrOwned<'_, T>> {
    option.as_ref().map(BorrowedOrOwned::Borrowed)
}

fn some_fail_message<T: Debug>(option: &Option<T>) -> String {
    format!("Expectation failed (expected Some)\n  actual: {:?}", option)
}

impl<'e, T, B> OptionExpectations<'e, T> for B
where
    T: Debug + 'e,
    B: ExpectationBuilder<'e, Value = Option<T>>,
{
    fn to_be_some(self) -> Self {
        self.to_pass(PredicateExpectation::new(
            (),
            |value: &Option<T>, _| value.is_some(),
            |_: &Option<T>, _| "Expectation failed (expected Some)\n  actual: None".to_string(),
        ))
    }

    fn to_be_none(self) -> Self {
        self.to_pass(PredicateExpectation::new(
            (),
            |value: &Option<T>, _| value.is_none(),
            |value: &Option<T>, _| {
                format!("Expectation failed (expected None)\n  actual: {:?}", value)
            },
        ))
    }

    fn to_be_some_matching<F>(self, predicate: F) -> Self
    where
        F: Fn(&T) -> bool + 'e,
    {
        self.to_pass(IsSomeMatchingExpectation(predicate))
    }
}

impl<'e, T, B> ProjectedOptionExpectations<'e, T> for B
where
    T: Debug + 'e,
    B: ExpectationBuilder<'e, Value = Option<T>>,
{
    fn to_be_some_and(self) -> ProjectedExpectationsBuilder<'e, Self, Option<T>, T> {
        let (expectation, expectations) =
            ProjectedExpectations::new(some_extract::<T>, some_fail_message::<T>);
        ProjectedExpectationsBuilder::from_expectation(self, expectation, expectations)
    }
}

/// Expectation for to_be_some_matching
struct IsSomeMatchingExpectation<F>(F);

impl<T: Debug, F: Fn(&T) -> bool> Expectation<Option<T>> for IsSomeMatchingExpectation<F> {
    fn check(&self, value: &Option<T>) -> CheckResult {
        match value {
            Some(v) => {
                if (self.0)(v) {
                    CheckResult::Pass
                } else {
                    CheckResult::Fail(format!(
                        "Expectation failed (expected Some value to match predicate)\n  actual: Some({:?})",
                        v
                    ))
                }
            }
            None => {
                CheckResult::Fail("Expectation failed (expected Some)\n  actual: None".to_string())
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::expect;
    use crate::expectations::EqualityExpectations;
    use crate::expectations::option::{OptionExpectations, ProjectedOptionExpectations};

    #[test]
    pub fn that_to_be_some_accepts_some_values() {
        // Given an Option that is Some
        let option: Option<i32> = Some(42);

        // Expect the to_be_some expectation to pass
        expect(option).to_be_some();
    }

    #[test]
    #[should_panic]
    pub fn that_to_be_some_does_not_accept_none_values() {
        // Given an Option that is None
        let option: Option<i32> = None;

        // Expect the to_be_some expectation to fail
        expect(option).to_be_some();
    }

    #[test]
    pub fn that_to_be_none_accepts_none_values() {
        // Given an Option that is None
        let option: Option<i32> = None;

        // Expect the to_be_none expectation to pass
        expect(option).to_be_none();
    }

    #[test]
    #[should_panic]
    pub fn that_to_be_none_does_not_accept_some_values() {
        // Given an Option that is Some
        let option: Option<i32> = Some(42);

        // Expect the to_be_none expectation to fail
        expect(option).to_be_none();
    }

    #[test]
    pub fn that_to_be_some_matching_accepts_some_values_that_match_predicate() {
        // Given an Option that is Some with a value that matches the predicate
        let option: Option<i32> = Some(42);

        // Expect the to_be_some_matching expectation to pass
        expect(option).to_be_some_matching(|v| *v > 40);
    }

    #[test]
    #[should_panic]
    pub fn that_to_be_some_matching_does_not_accept_some_values_that_do_not_match_predicate() {
        // Given an Option that is Some with a value that does not match the expectation
        let option: Option<i32> = Some(42);

        // Expect the to_be_some_matching expectation to fail
        expect(option).to_be_some_matching(|v| *v < 40);
    }

    #[test]
    #[should_panic]
    pub fn that_to_be_some_matching_does_not_accept_none_values() {
        // Given an Option that is None
        let option: Option<i32> = None;

        // Expect the to_be_some_matching expectation to fail
        expect(option).to_be_some_matching(|v| *v > 40);
    }

    #[test]
    pub fn that_to_be_some_and_accepts_some_values_with_matching_expectations() {
        // Given an Option that is Some with a value that matches the expectation
        let option: Option<i32> = Some(42);

        // Expect the to_be_some_and expectation to pass
        expect(option).to_be_some_and().to_equal(42);
    }

    #[test]
    #[should_panic]
    pub fn that_to_be_some_and_does_not_accept_some_values_with_non_matching_expectations() {
        // Given an Option that is Some with a value that does not match the expectation
        let option: Option<i32> = Some(42);

        // Expect the to_be_some_and expectation to fail
        expect(option).to_be_some_and().to_equal(43);
    }

    #[test]
    #[should_panic]
    pub fn that_to_be_some_and_does_not_accept_none_values() {
        // Given an Option that is None
        let option: Option<i32> = None;

        // Expect the to_be_some_and expectation to fail
        expect(option).to_be_some_and().to_equal(42);
    }
}