Expand description
§Moq
This library provides the procedural macro that generates a mock struct that implements trait.
§User guide
- Getting started
- Mock object name
async_trait
- Constants
- Associated types
- Default function implementation
- Impl trait
- Generics
- HRTB
- Restrictions
§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§
Attribute Macros§
- async_
trait - automock
- Macro that provides mock struct generating that implements trait