Crates.io | mry_macros |
lib.rs | mry_macros |
version | 0.8.0 |
source | src |
created_at | 2021-08-17 13:22:50.705645 |
updated_at | 2024-05-28 13:05:43.765657 |
description | Macro crate for mry, a simple but powerful mocking library that supports struct, trait, and function. |
homepage | https://github.com/ryo33/mry |
repository | https://github.com/ryo33/mry |
max_upload_size | |
id | 438465 |
size | 82,563 |
A simple but powerful mocking library for structs, traits, and function.
The clear difference between mry is that the API is simple and small, and since it is still being developed, you would find some behaviors that are not yet supported. Also, based on the principle of least astonishment, mry solves several problems of mockall in the simplest way.
Mry is cfg-free
In mockall, #[double]
is used to switch real and mocked structs.
The problem is that #[double]
makes mocked structs to be used for all test cases, so it will be complicated when some test case needs the real structs, especially for testing the struct itself.
In mry, no #[double]
or complex use
strategy is required.
Mry doesn't cause data races
In mockall, you need a manual synchronization with a mock of static functions and methods. The problem is that the result will be unpredictable and hard to debug when you forget to have a lock.
In mry, there is a managed synchronization, and when you forget it, you can get an error that tells you it is required.
#[mry::mry]
struct Cat {
name: String,
}
#[mry::mry]
impl Cat {
fn meow(&self, count: usize) -> String {
format!("{}: {}", self.name, "meow".repeat(count))
}
}
#[test]
fn meow_returns() {
let mut cat = mry::new!(Cat { name: "Tama".into() });
let mock_meow = cat.mock_meow(mry::Any).returns("Called".to_string());
assert_eq!(cat.meow(2), "Called".to_string());
mock_meow.assert_called(1);
}
a. If you have a mock object cat
: you can create a pattern for a method called meow
by calling cat.mock_meow
with a matcher for each argument.
// If you mock a struct called `Cat`
let mut cat = mry::new!(Cat { name: "Tama".into() });
// If you mock a trait called `Cat`
let mut cat = MockCat::default();
cat.mock_meow(mry::Any) // Any value can be matched
cat.mock_meow(3) // matched with 3
b. If you mock an associated function called new
in a struct Cat
, you can create a pattern with Cat::mock_new
.
Cat::mock_new(mry::Any)
c. If you mock a function called hello
, you can create a pattern with mock_hello
.
mock_hello(mry::Any)
[!NOTE] You can create multiple patterns for the same method or function, and they are matched in the order they are created.
Followed by the pattern, you can chain one of the following to set the expected behavior.
returns(value)
- Returns a value always. The value must implement Clone
for returning it multiple times.returns_once(value)
- Returns a value only once. No need to implement Clone
.returns_with(closure)
- Returns a dynamic value by a closure that takes the arguments. No need to implement Clone
for the output.calls_real_impl()
- Calls the real implementation of the method or function. Used for partial mocking.cat.mock_meow(3).returns("Called with 3".into());
Cat::mock_new(mry::Any).returns(cat);
mock_hello(mry::Any).returns("World".into());
You can call assert_called
for asserting the pattern is called as expected times.
cat.mock_meow(3).returns("Returns this string when called with 3".into());
assert_eq!(cat.meow(3), "Returns this string when called with 3".to_string());
cat.mock_meow(3).assert_called(1);
Also, you can count for a specific pattern.
cat.mock_meow(mry::Any).returns("Called".into());
assert_eq!(cat.meow(1), "Called".to_string());
assert_eq!(cat.meow(2), "Called".to_string());
assert_eq!(cat.meow(3), "Called".to_string());
cat.mock_meow(mry::Any)..assert_called(3);
// specific pattern
cat.mock_meow(2).assert_called(1);
If you want to assert the same pattern as the behavior setting, you can bind the result of the setting and call assert_called
on it.
let mock_meow = cat.mock_meow(3).returns("Called".to_string());
assert_eq!(cat.meow(3), "Called".to_string());
mock_meow.assert_called(1);
We need to add an attribute #[mry::mry]
in the front of the struct definition and the impl block to mock them.
#[mry::mry] // This
struct Cat {
name: &'static str,
}
#[mry::mry] // And this
impl Cat {
fn meow(&self, count: usize) -> String {
format!("{}: {}", self.name, "meow".repeat(count))
}
}
#[mry::mry]
adds a visible but ghostly field mry
to your struct, so your struct must be constructed by the following ways.
// An easy way
mry::new!(Cat { name: "Tama" })
// is equivalent to:
Cat {
name: "Tama",
mry: Default::default(),
};
// If you derive or impl Default trait.
Cat::default();
// or
Cat { name: "Tama", ..Default::default() };
[!IMPORTANT] When release build, the
mry
field of your struct will be zero sized, andmock_*
functions will be unavailable.
You can do partial mocking by using calls_real_impl()
.
#[mry::mry]
impl Cat {
fn meow(&self, count: usize) -> String {
self.meow_single().repeat(count)
}
fn meow_single(&self) -> String {
"meow".into()
}
}
#[test]
fn partial_mock() {
let mut cat: Cat = Cat {
name: "Tama".into(),
..Default::default()
};
cat.mock_meow_single().returns("hello".to_string());
cat.mock_meow(mry::Any).calls_real_impl();
// not "meowmeow"
assert_eq!(cat.meow(2), "hellohello".to_string());
}
Just add #[mry::mry]
to the trait definition.
#[mry::mry]
pub trait Cat {
fn meow(&self, count: usize) -> String;
}
Now we can use MockCat
as a mock object.
// You can construct it by Default trait
let mut cat = MockCat::default();
// API's are the same as the struct mocks.
cat.mock_meow(2).returns("Called with 2".into());
assert_eq!(cat.meow(2), "Called with 2".to_string());
Add #[mry::mry]
to the function definition.
#[mry::mry]
fn hello(count: usize) -> String {
"hello".repeat(count)
}
We need to acquire a lock of the function by using #[mry::lock(hello)]
because mocking of the static function uses a global state.
#[test]
#[mry::lock(hello)] // This is required!
fn function_keeps_original_function() {
// Usage is the same as the struct mocks.
mock_hello(Any).calls_real_impl();
assert_eq!(hello(3), "hellohellohello");
}
Include your associated function into the impl block with #[mry::mry]
.
#[mry::mry]
struct Cat {}
#[mry::mry]
impl Cat {
fn meow(count: usize) -> String {
"meow".repeat(count)
}
}
We need to acquire a lock for the same reason in the mocking function above.
#[test]
#[mry::lock(Cat::meow)] // This is required!
fn meow_returns() {
// Usage is the same as the struct mocks.
Cat::mock_meow(Any).returns("Called".to_string());
assert_eq!(Cat::meow(2), "Called".to_string());
}
To lock multiple static functions simultaneously, list the functions in a comma-separated format: #[mry::lock(function_a, function_b, function_c)]
. This approach automatically prevents deadlocks by sorting the functions before locking.
async fn
in trait (1.75.0 or later)Add #[mry::mry]
to the trait definition.
#[mry::mry]
pub trait Cat {
async fn meow(&self, count: usize) -> String;
}
You can do cat.mock_meow().returns("Called".to_string())
as the same as sync methods.
async fn
(1.75.0 or later)If you use trait_variant::make
attribute, you must put #[mry::mry]
under the #[trait_variant::make(Cat: Send)]
.
#[trait_variant::make(Cat: Send)]
#[mry::mry]
pub trait LocalCat {
async fn meow(&self) -> String;
}
let mut cat = MockLocalCat::default();
cat.mock_meow().returns("Called".to_string());
let mut cat = MockCat::default();
cat.mock_meow().returns("Called".to_string());
If you use async_trait
crate, you must put #[async_trait]
under the #[mry::mry]
.
#[mry::mry]
#[async_trait::async_trait]
pub trait Cat {
async fn meow(&self, count: usize) -> String;
}
You can do cat.mock_meow().returns("Called".to_string())
as the same as sync methods.
Mocking of impl trait is supported in the same API.
#[mry::mry]
impl Into<&'static str> for Cat {
fn into(self) -> &'static str {
self.name
}
}
You can do cat.mock_into()
as well as cat.mock_meow()
.
We can also mock a trait by manually creating a mock struct. If the trait has a generics or associated type, we need to use this way for now.
#[mry::mry]
#[derive(Default)]
struct MockIterator {}
#[mry::mry]
impl Iterator for MockIterator {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
todo!()
}
}