mockiato 0.9.6

A strict, yet friendly mocking library for Rust 2018
Documentation
use crate::matcher::ArgumentsMatcher;
use crate::method_call::{MethodCall, MethodCallBuilder};
use nameof::name_of;
use std::fmt::{self, Debug, Display};

#[cfg(rustc_is_nightly)]
const FOOTER: &str = "";
#[cfg(not(rustc_is_nightly))]
const FOOTER: &str = "\n\n----\nSome mockiato messages may not be displayed correctly. Re-run \
                      with nightly rust to see full error messages.\n----\n\n";

#[derive(Clone, Debug)]
enum ExpectedCallOrder {
    Sequentially,
    Unordered,
}

#[allow(missing_docs)]
pub struct Method<'mock, A, R>
where
    A: for<'args> ArgumentsMatcher<'args>,
{
    name: &'static str,
    calls: Vec<MethodCall<'mock, A, R>>,
    call_order: ExpectedCallOrder,
}

impl<'mock, A, R> Debug for Method<'mock, A, R>
where
    A: for<'args> ArgumentsMatcher<'args>,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct(name_of!(type Method<'mock, A, R>))
            .field(name_of!(name in Self), &self.name)
            .field(name_of!(calls in Self), &self.calls)
            .field(name_of!(call_order in Self), &self.call_order)
            .finish()
    }
}

impl<'mock, A, R> Clone for Method<'mock, A, R>
where
    A: for<'args> ArgumentsMatcher<'args>,
{
    fn clone(&self) -> Self {
        Self {
            name: self.name,
            calls: self.calls.clone(),
            call_order: self.call_order.clone(),
        }
    }
}

#[allow(missing_docs)]
impl<'mock, A, R> Method<'mock, A, R>
where
    A: for<'args> ArgumentsMatcher<'args>,
{
    pub fn new(name: &'static str) -> Self {
        Self {
            name,
            calls: Vec::new(),
            call_order: ExpectedCallOrder::Unordered,
        }
    }

    pub fn add_expected_call(&mut self, matcher: A) -> MethodCallBuilder<'mock, '_, A, R> {
        let call = MethodCall::new(matcher);

        self.calls.push(call);

        MethodCallBuilder::new(self.calls.last_mut().unwrap())
    }

    pub fn expect_method_calls_in_order(&mut self) {
        self.call_order = ExpectedCallOrder::Sequentially;
    }

    pub fn call_unwrap<'a>(&'a self, arguments: <A as ArgumentsMatcher<'a>>::Arguments) -> R {
        self.call(arguments)
            .unwrap_or_else(|err| panic!("\n\n{}{}\n", err, FOOTER))
    }

    pub fn verify_unwrap(&self) {
        self.verify()
            .unwrap_or_else(|err| panic!("{}{}", err, FOOTER))
    }

    fn call<'a>(
        &'a self,
        arguments: <A as ArgumentsMatcher<'a>>::Arguments,
    ) -> Result<R, CallError<'mock, 'a, A, R>> {
        match self.call_order {
            ExpectedCallOrder::Sequentially => {
                self.handle_call_with_sequentially_ordered_expectations(arguments)
            }
            ExpectedCallOrder::Unordered => self.handle_call_with_unordered_expectations(arguments),
        }
    }

    fn handle_call_with_sequentially_ordered_expectations<'a>(
        &'a self,
        arguments: <A as ArgumentsMatcher<'a>>::Arguments,
    ) -> Result<R, CallError<'mock, 'a, A, R>> {
        let matching_method_call = self.calls.iter().find(|call| call.accepts_more_calls());

        match matching_method_call {
            Some(matching_method_call)
                if matching_method_call.matches_expected_arguments(&arguments) =>
            {
                Ok(matching_method_call.call(arguments))
            }
            _ => Err(CallError::NoMatching(arguments, self)),
        }
    }

    fn handle_call_with_unordered_expectations<'a>(
        &'a self,
        arguments: <A as ArgumentsMatcher<'a>>::Arguments,
    ) -> Result<R, CallError<'mock, 'a, A, R>> {
        let matching_method_calls = self
            .calls
            .iter()
            .filter(|call| call.matches_expected_arguments(&arguments))
            .collect::<Vec<_>>();

        match matching_method_calls.len() {
            0 => Err(CallError::NoMatching(arguments, self)),
            1 => {
                let expected_call = matching_method_calls.first().unwrap();
                if expected_call.accepts_more_calls() {
                    Ok(expected_call.call(arguments))
                } else {
                    Err(CallError::NoMatching(arguments, self))
                }
            }
            _ => Err(CallError::MoreThanOneMatching(
                arguments,
                self,
                matching_method_calls,
            )),
        }
    }

    fn verify(&self) -> Result<(), VerificationError<'mock, '_, A, R>> {
        if self
            .calls
            .iter()
            .any(|method_call| !method_call.was_called_expected_number_of_times())
        {
            Err(VerificationError { method: self })
        } else {
            Ok(())
        }
    }
}

#[derive(Debug)]
enum CallError<'mock, 'a, A, R>
where
    A: for<'args> ArgumentsMatcher<'args>,
{
    NoMatching(
        <A as ArgumentsMatcher<'a>>::Arguments,
        &'a Method<'mock, A, R>,
    ),
    MoreThanOneMatching(
        <A as ArgumentsMatcher<'a>>::Arguments,
        &'a Method<'mock, A, R>,
        Vec<&'a MethodCall<'mock, A, R>>,
    ),
}

impl<'mock, 'a, A, R> Display for CallError<'mock, 'a, A, R>
where
    A: for<'args> ArgumentsMatcher<'args>,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            CallError::NoMatching(arguments, method) => {
                writeln!(f, "The call {}{} was not expected.", method.name, arguments)?;

                if method.calls.is_empty() {
                    writeln!(f, "No calls to {} were expected.", method.name)
                } else {
                    writeln!(
                        f,
                        "The following calls were expected:\n{}",
                        DisplayCalls(&method.calls.iter().collect::<Vec<_>>())
                    )
                }
            }
            CallError::MoreThanOneMatching(arguments, method, calls) => writeln!(
                f,
                "\nThe call {}{} matches more than one expected call:\n{}",
                method.name,
                arguments,
                DisplayCalls(calls)
            ),
        }
    }
}

#[derive(Debug)]
struct VerificationError<'mock, 'a, A, R>
where
    A: for<'args> ArgumentsMatcher<'args>,
{
    method: &'a Method<'mock, A, R>,
}

impl<'mock, 'a, A, R> Display for VerificationError<'mock, 'a, A, R>
where
    A: for<'args> ArgumentsMatcher<'args>,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(
            f,
            "The expected calls for {} were not satisified.",
            self.method.name
        )?;

        for call in &self.method.calls {
            writeln!(f, "{}", call)?;
        }

        Ok(())
    }
}

struct DisplayCalls<'mock, 'a, A, R>(&'a [&'a MethodCall<'mock, A, R>]);

impl<'mock, 'a, A, R> Display for DisplayCalls<'mock, 'a, A, R>
where
    A: for<'args> ArgumentsMatcher<'args>,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for call in self.0 {
            writeln!(f, "{}", call)?;
        }

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::arguments::ArgumentsMock;
    use crate::matcher::ArgumentsMatcherMock;

    #[test]
    fn call_errors_if_more_than_one_call_matches() {
        let mut method = Method::<_, ()>::new("test");

        method.add_expected_call(ArgumentsMatcherMock::new(Some(true)));

        method.add_expected_call(ArgumentsMatcherMock::new(Some(true)));

        match method.call(ArgumentsMock) {
            Err(CallError::MoreThanOneMatching(_, _, method_calls)) => {
                assert_eq!(2, method_calls.len());
            }
            _ => panic!("unexpected result from method call"),
        }
    }

    #[test]
    fn call_errors_if_no_calls_match() {
        let mut method = Method::<_, ()>::new("test");

        method
            .add_expected_call(ArgumentsMatcherMock::new(Some(false)))
            .returns(());

        match method.call(ArgumentsMock) {
            Err(CallError::NoMatching(..)) => {}
            _ => panic!("unexpected result from method call"),
        }
    }

    #[test]
    fn call_calls_matching_method_call() {
        let mut method = Method::<_, String>::new("test");

        method
            .add_expected_call(ArgumentsMatcherMock::new(Some(true)))
            .returns(String::from("bar"));

        assert_eq!(String::from("bar"), method.call(ArgumentsMock).unwrap());
    }

    #[test]
    fn errors_when_matching_call_is_called_more_than_expected() {
        let mut method = Method::<_, ()>::new("test");
        method
            .add_expected_call(ArgumentsMatcherMock::new(Some(true)))
            .times(1)
            .returns(());

        assert!(method.call(ArgumentsMock).is_ok());
        assert!(method.call(ArgumentsMock).is_err());
    }

    #[test]
    fn verify_is_ok_if_expectations_are_met() {
        let mut method = Method::<_, String>::new("test");

        method
            .add_expected_call(ArgumentsMatcherMock::new(Some(true)))
            .returns(String::default())
            .times(1);

        method.call(ArgumentsMock).unwrap();

        assert!(method.verify().is_ok());
    }

    #[test]
    fn verify_errors_if_expectations_not_met() {
        let mut method = Method::<_, String>::new("test");

        method
            .add_expected_call(ArgumentsMatcherMock::new(None))
            .returns(String::default())
            .times(2);

        assert!(method.verify().is_err());
    }

    #[test]
    fn verify_is_ok_if_expectations_are_empty() {
        let method = Method::<ArgumentsMatcherMock, String>::new("test");

        assert!(method.verify().is_ok());
    }

    #[test]
    fn unordered_expectations_work_with_one_matching_expected_call() {
        let mut method = Method::<_, ()>::new("test");

        method
            .add_expected_call(ArgumentsMatcherMock::new(Some(false)))
            .returns(());
        method
            .add_expected_call(ArgumentsMatcherMock::new(Some(true)))
            .returns(());

        let result = method.call(ArgumentsMock {});

        assert!(result.is_ok())
    }

    #[test]
    fn unordered_expectations_fail_with_multiple_matching_calls() {
        let mut method = Method::<_, ()>::new("test");

        method.add_expected_call(ArgumentsMatcherMock::new(Some(true)));
        method.add_expected_call(ArgumentsMatcherMock::new(Some(false)));
        method.add_expected_call(ArgumentsMatcherMock::new(Some(true)));

        let result = method.call(ArgumentsMock {});

        assert!(result.is_err())
    }

    #[test]
    fn ordered_expectations_fail_if_first_call_does_not_match() {
        let mut method = Method::<_, ()>::new("test");

        method
            .add_expected_call(ArgumentsMatcherMock::new(Some(false)))
            .returns(());
        method
            .add_expected_call(ArgumentsMatcherMock::new(None))
            .returns(());
        method.expect_method_calls_in_order();

        let result = method.call(ArgumentsMock {});

        assert!(result.is_err())
    }

    #[test]
    fn ordered_expectations_use_first_matching_call_regardless_of_other_expected_calls() {
        let mut method = Method::<_, ()>::new("test");

        method
            .add_expected_call(ArgumentsMatcherMock::new(Some(true)))
            .returns(());
        method
            .add_expected_call(ArgumentsMatcherMock::new(None))
            .returns(());
        method.expect_method_calls_in_order();

        let result = method.call(ArgumentsMock {});

        assert!(result.is_ok())
    }
}