Crate moq

source · []
Expand description

Moq

This library provides macro that generates mock struct that implements trait.

To get started, just add an attribute macro to the trait for which you want to generate a mock object:

#[moq::automock] // <-- this macro
trait TraitForMocking {
    fn func_that_i_want_to_test(&self, arg: String) -> Result<(), std::io::Error>;
}

Next, you can create a mock object and specify the expected behavior:

#[test]
fn some_test() {
    let mock = TraitForMockingMock::new()
        .expect_func_that_i_want_to_test(|arg: String| {
            assert_eq!(arg, "Hello, World!");
            Ok(())
        });

    assert!(mock.func_that_i_want_to_test("Hello, World!".to_owned()).is_ok());
}

Also supports async_trait:

#[moq::automock] // <- the main limitation is to specify
                 // the `automock` macro above the `async_trait`
#[async_trait::async_trait]
trait TraitForMocking {
   async fn func_that_i_want_to_test(&self, arg: String) -> Result<(), std::io::Error>;
}

#[tokio::test]
async fn some_test() {
    let mock = TraitForMockingMock::new()
        .expect_func_that_i_want_to_test(|arg: String| async {
            assert_eq!(arg, "Hello, World!");
            Ok(())
        });

    assert!(mock.func_that_i_want_to_test("Hello, World!".to_owned()).await.is_ok());
}

If function has arguments with lifetimes then moq::lambda! macro can help with it:

#[derive(Debug, Eq, PartialEq)]
struct Struct<'a>(&'a str);

#[moq::automock]
#[async_trait::async_trait]
trait TraitForMocking {
   async fn func_that_i_want_to_test<'a, 'b>(
        &self,
        arg1: Struct<'a>,
        arg2: Struct<'b>,
   ) -> Struct<'b>;
}

#[tokio::test]
async fn some_test() {
    let mock = TraitForMockingMock::new()
        .expect_func_that_i_want_to_test(moq::lambda!(
            async fn <'a, 'b>(arg1: Struct<'a>, arg2: Struct<'b>) -> Struct<'b> {
                assert_eq!(arg1, Struct("Hello"));
                assert_eq!(arg2, Struct("World"));
                arg2
            }
        ));

    assert_eq!(
        mock.func_that_i_want_to_test(
            Struct("Hello"),
            Struct("World"),
        ).await,
        Struct("World"),
    );
}

More info about moq::lambda! macro you will find below.

How its work

Generated mock struct checks that all specified expectation will calls in specified order. If f1() is called while f2() is expected, then there will be a panic.

#[moq::automock]
trait Trait {
    fn f1(&self);
    fn f2(&self);
}
let mock = TraitMock::new().expect_f2(||{});
mock.f1() // Panic

Checks if all expected functions are being called, if not then will be a panic.

#[moq::automock]
trait Trait {
    fn f1(&self);
    fn f2(&self);
}
let mock = TraitMock::new()
    .expect_f1(||{})
    .expect_f2(||{});
mock.f1()
// Panic when mock will drop

Attribute #[moq(default)] function

Specifies that needs to use the default implementation of the function:

#[moq::automock]
trait Trait {
    fn f1(&self);
     
    #[moq(default)]
    fn def(&self) {
        self.f1();
    }
}
let mock = TraitMock::new().expect_f1(|| {});
mock.def(); // Success

Attribute #[moq(default = "...")] associative type

Sets the associative type:

#[moq::automock]
trait Trait {
    #[moq(default = "i32")]
    type T;
}
let _: <TraitMock as Trait>::T = 42i32;

Attribute #[moq(default = ...)] const

Initializes or overrides default const value with specified value:

#[moq::automock]
trait Trait {
    #[moq(default = 42)]
    const CONST: i32;
}
assert_eq!(<TraitMock as Trait>::CONST, 42);

Attribute #[moq(default_with = "...")] const

Initializes or overrides default const value with specified const function:

#[moq::automock]
trait Trait {
    #[moq(default_with = "some_func")]
    const CONST: i32;
}
const fn some_func() -> i32 { 42 }
assert_eq!(TraitMock::CONST, 42);

Macro moq::lambda!(...)

Helpful macro for generating closure with specified lifetimes. In rust you can’t specify lifetimes for closure but you should, because lifetime inference in closure is dumb. Then this macro will help. Currently, capturing external variables is not possible, but this is in the plans.

Syntax is like anonymous function:

#[derive(Debug, PartialEq)]
struct StructNamed {
   field: i32,
}

impl StructNamed {
fn get_field(&self) -> i32 {
       self.field
   }
}

struct StructUnnamed(i32);

let arg1 = 1i32;
let arg2 = 2i32;
let arg3 = StructNamed { field: 3i32 };
let arg4 = StructNamed { field: 4i32 };
let arg5 = StructNamed { field: 5i32 };
let arg6 = StructNamed { field: 6i32 };
let arg7 = StructUnnamed(7i32);

moq::lambda!(
// Environment capturing (optional)
//
//      expression          var alias expression type
//      |                   |         |
//      vvvvvvvvvvvvvvvv    vvvvvv    vvvvvvvvvvv
    [
        arg1                       => i32,
        arg2             as field0 => i32,
        arg3                       => StructNamed,
        arg4.field                 => i32,
        arg5.field       as field2 => i32,
        arg6.get_field() as field3 => i32,
        arg7.0           as field4 => i32
    ]

// Function signature
//  
//  asyncness
//  |        generic params (lifetimes)
//  |        |    object that captured env, needs only if env specified
//  |        |    |      arguments
//  |        |    |      |                return type
//  |        |    |      |                |
//  vvvvv    vvvv vvvvv  vvvvvvvvvvvv     vvvvvvvvvvvv
    async fn <'a>(&self, arg: &'a str) -> &'static str {
        assert_eq!(self.arg1, 1);
        assert_eq!(self.field0, 2);
        assert_eq!(self.arg3, StructNamed { field: 3 });
        assert_eq!(self.field, 4);
        assert_eq!(self.field2, 5);
        assert_eq!(self.field3, 6);
        assert_eq!(self.field4, 7);
        // ...
    }
)

It turns to something like that:

{
    struct __MoqLambda {
        arg1: i32,
        field0: i32,
        arg3: StructNamed,
        field: i32,
        field2: i32,
        field3: i32,
        field4: i32,
    }
    impl __MoqLambda {
        fn new(
            arg1: i32,
            field0: i32,
            arg3: StructNamed,
            field: i32,
            field2: i32,
            field3: i32,
            field4: i32,
        ) -> Self {
            Self {
                arg1,
                field0,
                arg3,
                field,
                field2,
                field3,
                field4,
            }
        }
    }
    #[::moq::async_trait]
    impl<'a> ::moq::AsyncFunc<(&'a str,), &'static str> for __MoqLambda {
        async fn call<'__moq>(&'__moq self, (arg,): (&'a str,)) -> &'static str
        where (&'a str,): '__moq,
              &'static str: '__moq
        {
            // ...
        }
    }
    __MoqLambda::new(
        arg1,             
        arg2,             
        arg3,             
        arg4.field,       
        arg5.field,       
        arg6.get_field(),
        arg7.0,           
    )
}

Macros

Helpful macro for generating closure with specified lifetimes. In rust you can’t specify lifetimes for closure but you should, because lifetime inference in closure is dumb. Then this macro will help. Currently, capturing external variables is not possible, but this is in the plans.

Traits

Attribute Macros

Macro that provides mock struct generating that implements trait