Mry
A simple but powerful mocking library for structs, traits, and function.
Features
- A really simple and easy API
- Supports mock of structs, traits, and functions.
- No need to switch between mock objects and real objects.
- Supports partial mocking.
Compared to mockall
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.
Example
How to mock a method or function
Step1. Creating a pattern for a method or function
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 = new!;
// If you mock a trait called `Cat`
let mut cat = default;
cat.mock_meow // Any value can be matched
cat.mock_meow // 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
.
mock_new
c. If you mock a function called hello
, you can create a pattern with mock_hello
.
mock_hello
[!NOTE] You can create multiple patterns for the same method or function, and they are matched in the order they are created.
Step 2. Setting an expected behavior for the pattern
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 implementClone
for returning it multiple times.returns_once(value)
- Returns a value only once. No need to implementClone
.returns_with(closure)
- Returns a dynamic value by a closure that takes the arguments. No need to implementClone
for the output.returns_ready(value)
- Returns a boxed future every time with cloning the value.returns_ready_once(value)
- Shortcut forreturns_once(Box::new(async { value }))
.calls_real_impl()
- Calls the real implementation of the method or function. Used for partial mocking.
[!NOTE]
returns_ready
andreturns_ready_once
is not forasync fn
, but for methods that return a boxed future or trait methods with return-positionimpl Future
.
cat.mock_meow.returns;
mock_new.returns;
mock_hello.returns;
(Optional) Step3. Asserting the pattern is called as expected times
You can call assert_called
for asserting the pattern is called as expected times.
// You need to bind the mock object to a variable to call `assert_called`.
let mock_meow = cat.mock_meow.returns;
assert_eq!;
mock_meow.assert_called;
Also, you can count for a specific pattern without setting a behavior.
// You must create a pattern before `cat.meow` would be called.
let mock_meow_for_2 = cat.mock_meow;
let mock_meow_for_any = cat.mock_meow.returns;
assert_eq!;
assert_eq!;
assert_eq!;
mock_meow_for_2.assert_called;
mock_meow_for_any.assert_called;
Basic Usages
Mocking a struct
We need to add an attribute #[mry::mry]
in the front of the struct definition and the impl block to mock them.
// This
// And this
#[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
new!
// is equivalent to:
Cat ;
// If you derive or impl Default trait.
default;
// or
Cat ;
[!IMPORTANT] When release build, the
mry
field of your struct will be zero sized, andmock_*
functions will be unavailable.
Partial mocks
You can do partial mocking by using calls_real_impl()
.
Mocking a trait
Just add #[mry::mry]
to the trait definition.
Now we can use MockCat
as a mock object.
// You can construct it by Default trait
let mut cat = default;
// API's are the same as the struct mocks.
cat.mock_meow.returns;
assert_eq!;
Mocking a function
Add #[mry::mry]
to the function definition.
We need to acquire a lock of the function by using #[mry::lock(hello)]
because mocking of the static function uses a global state.
// This is required!
Mocking an associated function (static function)
Include your associated function into the impl block with #[mry::mry]
.
We need to acquire a lock for the same reason in the mocking function above.
// This is required!
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.
Advanced Usages
async fn
in trait (1.75.0 or later)
Add #[mry::mry]
to the trait definition.
You can do cat.mock_meow().returns("Called".to_string())
as the same as sync methods.
trait_variant::make with 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)]
.
let mut cat = default;
cat.mock_meow.returns;
let mut cat = default;
cat.mock_meow.returns_ready;
[!IMPORTANT] You must use
returns_ready
for the generated one instead ofreturns
for the original one. Because the generated one is notasync fn
butimpl Future
in return position, and mry expects a boxed future for returning asimpl Future
.
async_trait
If you use async_trait
crate, you must put #[async_trait]
under the #[mry::mry]
.
You can do cat.mock_meow().returns("Called".to_string())
as the same as sync methods.
impl Trait for Struct
Mocking of impl trait is supported in the same API.
You can do cat.mock_into()
as well as cat.mock_meow()
.
Mocking a trait with generics or associated type
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.