mry 0.4.8

A simple but powerful mocking library that supports struct, trait, and function.
Documentation

Mry

GitHub MIT/Apache 2.0 Crates.io docs.rs GitHub Repo stars

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 of switching between mock objects and real objects.
  • Supports partial mocking.

Compared to mockall

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.

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",
    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.

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(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

Release build

When release build, the mry field of your struct will be zero size, and mock_* functions will be unavailable.

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!()
    }
}

async_trait

Add #[mry::mry] with the async_trait attribute underneath.

#[mry::mry]
#[async_trait::async_trait]
pub trait Cat {
    async fn meow(&self, count: usize) -> String;
}

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

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.