galvanic-mock 0.1.3

A behaviour-driven mocking framework for generic traits. Create mocks for (multiple) traits in a behaviour-driven development mocking framework. Define the behaviour of mocks and expected method calls using argument patterns. Supports mocking of generic traits and generic methods. Requires: nightly
Documentation
/* Copyright 2017 Christopher Bacher
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
use syn;
use syn::parse::*;

use data::*;
use generate::binding_implementer::implement_initialize_binding;


named!(pub parse_bind -> BindingField,
    do_parse!(
        punct!("bind") >> name: call!(syn::parse::ident) >>
        punct!(":") >> ty: call!(syn::parse::ty) >>
        punct!("=") >> initializer: call!(syn::parse::expr) >>
        (BindingField { name, ty, initializer })
    )
);

named!(parse_given_func -> (syn::Ident, BehaviourMatcher, Return, GivenRepeat),
    do_parse!(
        method: call!(syn::parse::ident) >>
        args: alt!(
              delimited!(punct!("("), separated_list!(punct!(","), syn::parse::expr), punct!(")")) => {
                  |es: Vec<_>|
                    if es.is_empty() { BehaviourMatcher::Void }
                    else { BehaviourMatcher::PerArgument(es) }
              }
            | call!(syn::parse::expr) => { |e| BehaviourMatcher::Explicit(e) }
        ) >>
        return_stmt: alt!( preceded!(keyword!("then_return"), syn::parse::expr) => { |e| Return::FromValue(e) }
                         | preceded!(keyword!("then_return_from"), syn::parse::expr) => { |e| Return::FromCall(e) }
                         | preceded!(keyword!("then_return_ref"), syn::parse::expr) => { |e| Return::FromValue(e) }
                         | preceded!(keyword!("then_return_ref_from"), syn::parse::expr) => { |e| Return::FromCall(e) }
                         | keyword!("then_spy_on_object") => { |_| Return::FromSpy }
                         | keyword!("then_panic") => { |_| Return::Panic }
        ) >>
        repeat: alt!( preceded!(keyword!("times"), syn::parse::expr) => { |e| GivenRepeat::Times(e) }
                    | keyword!("always") => { |_| GivenRepeat::Always }
        ) >>
        (method, args, return_stmt, repeat)
    )
);

named!(pub parse_given -> Vec<GivenStatement>,
    do_parse!(
        punct!("<") >> mock_var: call!(syn::parse::ident) >> keyword!("as") >> ufc_trait: call!(syn::parse::path) >> punct!(">") >>
        punct!("::") >>
        func: parse_given_func >>
        (vec![GivenStatement {
            block_id: 0,
            stmt_id: 0,
            mock_var,
            ufc_trait,
            method: func.0,
            matcher: func.1,
            return_stmt: func.2,
            repeat: func.3
        }])
    )
);

named!(pub parse_given_trait_block -> Vec<GivenStatement>,
    do_parse!(
        punct!("<") >> mock_var: call!(syn::parse::ident) >> keyword!("as") >> ufc_trait: call!(syn::parse::path) >> punct!(">") >>
        punct!("::") >> punct!("{") >>
        statements: terminated_list!(punct!(";"), do_parse!(
            func: parse_given_func >>
            (GivenStatement {
                block_id: 0,
                stmt_id: 0,
                mock_var: mock_var.clone(),
                ufc_trait: ufc_trait.clone(),
                method: func.0,
                matcher: func.1,
                return_stmt: func.2,
                repeat: func.3
            })
        )) >> punct!("}") >>
        (statements)
    )
);

named!(pub parse_givens -> (Vec<BindingField>, Vec<GivenStatement>),
    delimited!(tuple!(keyword!("given"), punct!("!"), punct!("{")),
               tuple!(
                   terminated_list!(punct!(";"), parse_bind),
                   map!(terminated_list!(punct!(";"), alt!(parse_given | parse_given_trait_block)),
                        |statements_list: Vec<Vec<GivenStatement>>| statements_list.into_iter().flat_map(|stmts| stmts.into_iter()).collect::<Vec<_>>()
                   )
               ),
               punct!("}")
    )
);


pub fn handle_given(source: &str, absolute_position: usize) -> (String, String) {
    if let IResult::Done(remainder, (binding_fields, given_definitions)) = parse_givens(source) {
        let mut statements = acquire!(GIVEN_STATEMENTS);

        let mut add_statements = Vec::new();
        for (idx, mut stmt) in given_definitions.into_iter().enumerate() {
            stmt.block_id = absolute_position;
            stmt.stmt_id = absolute_position + idx;
            let stmt_id = stmt.stmt_id;

            {
                let mock_var = &stmt.mock_var;
                let ufc_trait_name = stmt.trait_name();
                let method_name = stmt.method_name();

                let stmt_repr = format!("{}", stmt);
                add_statements.push(match &stmt.repeat {
                    &GivenRepeat::Always => quote!( #mock_var.add_given_behaviour(#ufc_trait_name, #method_name, mock::GivenBehaviour::with(#stmt_id, binding.clone(), #stmt_repr)); ),
                    &GivenRepeat::Times(ref expr) => quote!( #mock_var.add_given_behaviour(#ufc_trait_name, #method_name, mock::GivenBehaviour::with_times(#expr, #stmt_id, binding.clone(), #stmt_repr)); ),
                });
            }

            statements.entry(stmt.ufc_trait.clone())
                      .or_insert_with(|| Vec::new())
                      .push(stmt);
        }

        let binding = Binding {
            block_id: absolute_position,
            fields: binding_fields
        };
        let binding_initialization = implement_initialize_binding(&binding);
        acquire!(BINDINGS).push(binding);

        let given_block = quote! {
            let binding = std::rc::Rc::new(#binding_initialization);
            #(#add_statements)*
        };

        return (given_block.to_string(), remainder.to_owned());
    } else { panic!("Expecting a `given!` definition: <MOCK_VAR_NAME as MOCKED_TRAIT>::METHOD(MATCHER, ...) THEN REPEAT; ..."); }
}


#[cfg(test)]
mod test {
    use galvanic_assert::*;
    use galvanic_assert::matchers::*;

    mod parsers {
        use super::*;
        use super::super::*;

        #[test]
        fn should_parse_bind() {
            let field = parse_bind("bind x: i32 = 1 + 2").expect("");

            assert_that!(&field.name, eq(syn::Ident::from("x")));
            assert_that!(&field.ty, is_variant!(syn::Ty::Path));
            assert_that!(&field.initializer.node, is_variant!(syn::ExprKind::Binary));
        }

        #[test]
        fn should_parse_given_return_from_call() {
            let stmt = &parse_given("<mock as MyTrait>::foo() then_return_from || { 2 } always").expect("")[0];

            assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock")));
            assert_that!(&stmt.method, eq(syn::Ident::from("foo")));
            // assert_that!(args.is_empty(), otherwise "some arguments are detected");
            assert_that!(&stmt.return_stmt, eq(Return::FromCall(syn::parse::expr("|| { 2 }").expect(""))));
            assert_that!(&stmt.repeat, eq(GivenRepeat::Always));
        }

        #[test]
        fn should_parse_given_with_universal_function_call_syntax() {
            let stmt = &parse_given("<mock as MyTrait>::foo() then_return_from || { 2 } always").expect("")[0];

            assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock")));
            assert_that!(&stmt.ufc_trait, eq(syn::parse::path("MyTrait").expect("Could not parse expected type")));
            assert_that!(&stmt.method, eq(syn::Ident::from("foo")));
            // assert_that!(args.is_empty(), otherwise "some arguments are detected");
            assert_that!(&stmt.return_stmt, eq(Return::FromCall(syn::parse::expr("|| { 2 }").expect(""))));
            assert_that!(&stmt.repeat, eq(GivenRepeat::Always));
        }

        #[test]
        fn should_parse_given_spy_on_object() {
            let stmt = &parse_given("<mock as MyTrait>::foo() then_spy_on_object always").expect("")[0];

            assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock")));
            assert_that!(&stmt.method, eq(syn::Ident::from("foo")));
            // assert_that!(args.is_empty(), otherwise "some arguments are detected");
            assert_that!(&stmt.return_stmt, eq(Return::FromSpy));
            assert_that!(&stmt.repeat, eq(GivenRepeat::Always));
        }

        #[test]
        fn should_parse_given_panic() {
            let stmt = &parse_given("<mock as MyTrait>::foo() then_panic always").expect("")[0];

            assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock")));
            assert_that!(&stmt.method, eq(syn::Ident::from("foo")));
            // assert_that!(args.is_empty(), otherwise "some arguments are detected");
            assert_that!(&stmt.return_stmt, eq(Return::Panic));
            assert_that!(&stmt.repeat, eq(GivenRepeat::Always));
        }

        #[test]
        fn should_parse_given_times() {
            let stmt = &parse_given("<mock as MyTrait>::foo() then_return 1 times 2").expect("")[0];

            assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock")));
            assert_that!(&stmt.method, eq(syn::Ident::from("foo")));
            // assert_that!(args.is_empty(), otherwise "some arguments are detected");
            assert_that!(&stmt.return_stmt, eq(Return::FromValue(syn::parse::expr("1").expect(""))));
            assert_that!(&stmt.repeat, eq(GivenRepeat::Times(syn::parse::expr("2").expect(""))));
        }

        #[test]
        fn should_parse_given_always() {
            let stmt = &parse_given("<mock as MyTrait>::foo() then_return 1 always").expect("")[0];

            assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock")));
            assert_that!(&stmt.method, eq(syn::Ident::from("foo")));
            // assert_that!(args.is_empty(), otherwise "some arguments are detected");
            assert_that!(&stmt.return_stmt, eq(Return::FromValue(syn::parse::expr("1").expect(""))));
            assert_that!(&stmt.repeat, eq(GivenRepeat::Always));
        }

        #[test]
        fn should_parse_given_args() {
            let stmt = &parse_given("<mock as MyTrait>::foo(2, 4) then_return 1 always").expect("")[0];

            assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock")));
            assert_that!(&stmt.method, eq(syn::Ident::from("foo")));
            // assert_that!(&args, collection::contains_in_order(vec![syn::parse::expr("2").expect(""), syn::parse::expr("4").expect("")]));
            assert_that!(&stmt.return_stmt, eq(Return::FromValue(syn::parse::expr("1").expect(""))));
            assert_that!(&stmt.repeat, eq(GivenRepeat::Always));
        }

        #[test]
        fn should_parse_given_matcher() {
            let stmt = &parse_given("<mock as MyTrait>::foo |a,b| true then_return 1 always").expect("")[0];

            assert_that!(&stmt.mock_var, eq(syn::Ident::from("mock")));
            assert_that!(&stmt.method, eq(syn::Ident::from("foo")));
            // assert_that!(&args, collection::contains_in_order(vec![syn::parse::expr("2").expect(""), syn::parse::expr("4").expect("")]));
            assert_that!(&stmt.return_stmt, eq(Return::FromValue(syn::parse::expr("1").expect(""))));
            assert_that!(&stmt.repeat, eq(GivenRepeat::Always));
        }

        #[test]
        fn should_parse_givens() {
            let (binds, givens) = parse_givens("given! { <mock as MyTrait>::foo() then_return 1 always; }").expect("");

            assert_that!(&binds.len(), eq(0));
            assert_that!(&givens.len(), eq(1));
        }

        #[test]
        fn should_parse_givens_with_block() {
            let (binds, givens) = parse_givens("given! { <mock as MyTrait>::foo() then_return 1 always; <mock as MyTrait>::{ foo() then_return 1 always; foo() then_return 1 always; }; }").expect("");

            assert_that!(&binds.len(), eq(0));
            assert_that!(&givens.len(), eq(3));
        }

        #[test]
        fn should_parse_givens_with_bind() {
            let (binds, givens) = parse_givens("given! { bind x: i32 = 1; bind x: f32 = 2.0; <mock as MyTrait>::foo() then_return 1 always; }").expect("");
            parse_givens("given ! { < x as TestTrait < i32 , f64 >>::func ( eq ( 2 ) , eq ( 2.2 ) ) then_return 12 always ; }").expect("");

            assert_that!(&binds.len(), eq(2));
            assert_that!(&givens.len(), eq(1));
        }
    }
}