I've released 0.1.8 of mry, a cfg(test)-free mocking library. It newly supports mocking functions and associated functions. so now it's capable of mocking structs, traits, and functions.
Features
- No need of switching between mock objects and real objects such as the way using
#[cfg(test)]
. - Supports mocking for structs, traits, and functions.
- Supports partial mocking.
Example
#[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() });
cat.mock_meow(mry::Any).returns("Called".to_string());
assert_eq!(cat.meow(2), "Called".to_string());
}
Mocking a struct
We need to add an attribute #[mry::mry]
in the front of struct definition and 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",
#[cfg(test)]
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_*(...).ruturns_with(|arg| ...)
: Makes a mock to return a value with a closure (This is allowed to return!Clone
unlikereturns
cannot). -
mock_*(...).assert_called()
: Assert that a mock was called with correct arguments (and times).
Examples
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
cat.mock_meow(3).assert_called(); // Assert called with 3
cat.mock_meow(mry::Any).assert_called(); // Assert called with any value
cat.mock_meow(3).assert_called().times(1); // exactly called 1 time with 3
cat.mock_meow(3).assert_called().times_within(0..100); // or within the range
impl Trait for Struct
Also, 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
}
}
Partial mocks
You can do partial mocking with 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());
}
Mocking a trait
Just add #[mry::mry]
as before;
#[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());
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.
#[mry::mry]
#[derive(Default)]
struct MockIterator {
}
#[mry::mry]
impl Iterator for MockIterator {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
todo!()
}
}
Mocking a function
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 static function uses 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");
}
Mocking a associated function (static function)
Include your associated function into the impl block with #[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 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());
}