Crate moq

Source
Expand description

§Moq

This library provides the procedural macro that generates a mock struct that implements trait.

§User guide

§Getting started

To get started, 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 behaviour:

#[test]
fn some_test() {
    let mock = MockTraitForMocking::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());
}
§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 = MockTrait::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 = MockTrait::new()
    .expect_f1(||{})
    .expect_f2(||{});
mock.f1()
// Panic when mock will drop

§Mock object name

By default, mock object name will be Mock%TRAIT%, for example:

// For trait:
#[moq::automock]
trait Trait { /*...*/ }

// A mock will be generated:
struct MockTrait { /*...*/ }

If you want to customise it, then use rename attribute:

#[moq::automock(rename = "FooBar")]
trait Trait { /*...*/ }

// A mock will be generated:
struct FooBar { /*...*/ }

You can also add an attribute directly to a trait:

#[moq::automock]
#[moq(rename = "FooBar")]
trait Trait { /*...*/ }

// A mock will be generated:
struct FooBar { /*...*/ }

§async_trait

automock 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 = MockTraitForMocking::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());
}

§Constants

There are 3 ways to specify the value of constants:

  • By using the default attribute and specifying the ‘as is’ expression
  • By using the default_with attribute and specifying the path to the constant function
  • Through the default value
const fn const_str_default() -> &'static str { "string" }

#[moq::automock]
trait Trait {
    #[moq(default_with = "const_str_default")]
    const CONST_STR: &'static str;
    #[moq(default = 1)]
    const CONST_INT: i32;
    const CONST_DEFAULT: usize = 42;
}

assert_eq!(MockTrait::CONST_STR, "string");
assert_eq!(MockTrait::CONST_INT, 1);
assert_eq!(MockTrait::CONST_DEFAULT, 42);

§Associated types

You can specify the type of associated type by using the default attribute and specifying the type:

#[moq::automock]
trait Trait {
    #[moq(default = "String")]
    type AssocType: Clone;
}

let _: String = <MockTrait as Trait>::AssocType::from("string");

§Default function implementation

If your trait has default implementation of function, then by default automock generate independent implementation:

#[moq::automock]
trait Trait {
    fn f1(&self);

    fn f2(&self) {
        self.f1();
    }
}

let mock = MockTrait::new()
    .expect_f1(|| {})
    .expect_f2(|| {});

mock.f1();
mock.f2();
// Success

If you want to use default implementation of function, you need to mark function by default attribute:

#[moq::automock]
trait Trait {
    fn f1(&self);

    #[moq(default)]
    fn f2(&self) {
        self.f1();
    }
}

let mock = MockTrait::new()
    .expect_f1(|| {})
    .expect_f1(|| {}); // <- attention here

mock.f1();
mock.f2();
// Success

§Impl trait

If you using RPITIT (Return Position Impl Trait In Trait), automock generates mock with boxed type:

trait DummyTrait { fn dummy(&self) -> String; }
impl DummyTrait for i32 { fn dummy(&self) -> String { format!("{self}") } }
impl DummyTrait for &'static str { fn dummy(&self) -> String { format!("{self}") } }
impl DummyTrait for Box<dyn DummyTrait> { fn dummy(&self) -> String { (**self).dummy() } }

#[moq::automock]
trait Trait {
    fn f(&self) -> impl DummyTrait;
}

let mock = MockTrait::new()
    .expect_f(|| -> Box<dyn DummyTrait> {
        Box::new(42i32)
    })
    .expect_f(|| -> Box<dyn DummyTrait> {
        Box::new("string")
    });

assert_eq!(mock.f().dummy(), "42");
assert_eq!(mock.f().dummy(), "string");

If boxed dyn trait doesn’t suit you, you can specify a specific type by output attribute:

trait DummyTrait { fn dummy(&self) -> String; }
impl DummyTrait for i32 { fn dummy(&self) -> String { format!("{self}") } }

#[moq::automock]
trait Trait {
    #[moq(output = "i32")]
    fn f(&self) -> impl DummyTrait;
}

let mock = MockTrait::new()
    .expect_f(|| -> i32 { 42i32 });

assert_eq!(mock.f().dummy(), "42");

output attribute also supports infer syntax, may be useful for async:


#[moq::automock]
trait Trait {
    #[moq(output = "Pin<_>")]
    fn f(&self) -> impl Future<Output = i32>;
}

let mock = MockTrait::new()
    .expect_f(|| -> Pin<Box<dyn Future<Output = i32>>> {
        Box::pin(async { 42i32 })
    });

assert_eq!(mock.f().await, 42);

§Generics

Mocking generic methods is possible, but there are restrictions:

  • Generic parameter must be 'static (maybe it will be fixed in the future)
  • Expectation function generic must be specified explicitly

#[moq::automock]
trait Trait {
    fn f<T>(&self, arg: T) where T: Display + 'static;
}

let mock = MockTrait::new()
    .expect_f(|arg: i32| assert_eq!(arg, 123)); // <- explicit arg type

mock.f(123);

§HRTB

In rust you can’t specify lifetimes for closure but you should, because lifetime inference in closure is dumb. Then moq::lambda!(...) macro will help.

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

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

let mock = MockTrait::new()
    .expect_f(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.f(
        Struct("Hello"),
        Struct("World"),
    ).await,
    Struct("World"),
);

§Restrictions

  • Generics must be T: 'static
  • Expectation function with generics should accept specific types (mock.expect_f::<T, _>(|arg: T| {}))
  • Anonymized lifetimes not allowed (&'_ T), make them explicit
  • Impl trait in argument position not allowed (arg: impl Trait), use generics instead
  • Static functions not supported yet

Macros§

lambda
Helpful macro for generating closure with specified lifetimes.

Traits§

AsyncFunc
Func

Attribute Macros§

async_trait
automock
Macro that provides mock struct generating that implements trait