A simple but powerful mocking library for structs, traits, and function.
The clear difference of 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 priciple of least astonishment, mry solves several problems of mockall by 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 mock of static functions and methods. The problem is that the result will be unpredicted 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 it is required.
```rust
struct Cat { name: String, }
impl Cat { fn meow(&self, count: usize) -> String { format!("{}: {}", self.name, "meow".repeat(count)) } }
fn meow_returns() { let mut cat = mry::new!(Cat { name: "Tama".into() });
cat.mock_meow(mry::Any).returns("Called".to_string());
assert_eq!(cat.meow(2), "Called".to_string());
} ```
We need to add an attribute #[mry::mry]
in the front of struct definition and impl block to mock them.
```rust
struct Cat { name: &'static str, }
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.
```rust // 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() }; ```
Now you can mock it by using following functions:
mock_*(...).returns(...)
: Makes a mock to return a constant value.mock_*(...).returns_with(|arg| ...)
: Makes a mock to return a value with a closure (This is allowed to return !Clone
unlike returns
cannot).mock_*(...).assert_called(...)
: Asserts that a mock was called with correct arguments and times, and returns call logs.rust
cat.mock_meow(3).returns("Returns this string when called with 3".into());
cat.mock_meow(mry::Any).returns("This string is returned for any value".into());
cat.mock_meow(mry::Any).returns_with(|count| format!("Called with {}", count)); // return a dynamic value
rust
cat.mock_meow(3).assert_called(1); // Assert called exactly 1 time with 3
cat.mock_meow(mry::Any).assert_called(1); // Assert called with any value
cat.mock_meow(3).assert_called(0..100); // or within the range
When release build, the mry
field of your struct will be zero size, and mock_*
functions will be unavailable.
Also, mocking of impl trait is supported in the same API.
```rust
impl Into<&'static str> for Cat { fn into(self) -> &'static str { self.name } } ```
You can do partial mocking with using calls_real_impl()
.
```rust
impl Cat { fn meow(&self, count: usize) -> String { self.meow_single().repeat(count) }
fn meow_single(&self) -> String {
"meow".into()
}
}
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]
as before;
```rust
pub trait Cat { fn meow(&self, count: usize) -> String; } ```
Now we can use MockCat
as a mock object.
```rust // 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());
asserteq!(cat.meow(2), "Called with 2".tostring()); ```
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.
```rust
struct MockIterator { }
impl Iterator for MockIterator { type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
todo!()
}
} ```
Add #[mry::mry]
with the async_trait attribute underneath.
```rust
pub trait Cat { async fn meow(&self, count: usize) -> String; } ```
Add #[mry::mry]
to the function definition.
```rust
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 static function uses global state.
```rust
fn functionkeepsoriginalfunction() { // Usage is the same as the struct mocks. mockhello(Any).callsrealimpl();
assert_eq!(hello(3), "hellohellohello");
} ```
Include your associated function into the impl block with #[mry::mry]
.
```rust struct Cat {}
impl Cat { fn meow(count: usize) -> String { "meow".repeat(count) } } ```
We need to acquire a lock for the same reason in mocking function above.
```rust
fn meowreturns() { // Usage is the same as the struct mocks. Cat::mockmeow(Any).returns("Called".to_string());
assert_eq!(Cat::meow(2), "Called".to_string());
} ```