Mry: A cfg(test)-free mocking library for structs, traits, and function

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 unlike returns 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());
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.