Announcing Mockiato - A strict, yet friendly mocking library for Rust 2018
Update: Mockiato now works with stable rust.
⚠️ Disclaimer for working with stable rust
Mockiato relies on the unstable
proc_macro_diagnostics
API to print helpful messages and the unstablespecialization
feature to be able to print expected calls.Mocks work as expected on stable rust, but diagnostics are very limited.
We recommend re-running failing tests using nighly rust in order to pin-point the issue.
We’re proud to announce mockiato! For the last few months, we tackled the issue of creating a usable mocking library. Our primary goals were
- Ease of use: The mocks are written in idiomatic Rust and don’t rely on custom macro syntax.
- Maintainability: The entire code base strives to follow the rules of Clean Code and Clean Architecture as specified by Robert C. Martin.
- Strict expectation enforcement: Mockiato catches unexpected behavior as soon as it happens instead of returning default values.
Relying on mocks instead of implementations allows you to test components in isolation of their dependencies, providing real unit testing capabilities.
Mockiato enjoys a broad test suite, composed of many unit tests and integration tests.
Table of Contents
The Basics
Annotating a trait Greeter
with #[mockable]
generates a struct GreeterMock
that implements Greeter
and provides an expect_<method_name>
method for every method in the trait.
Calling these expect
methods sets up expectations which panic when invoking an
unexpected call or when GreeterMock
goes out of scope with unmet expectations.
#[cfg(test)]
use mockiato::mockable;
#[cfg_attr(test, mockable)]
trait Greeter {
fn greet(&self, name: &str);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn greet_the_world() {
let mut greeter = GreeterMock::new();
greeter.expect_greet(|arg| arg.partial_eq("world"));
greeter.greet("world");
}
}
Expecting a method call a certain amount of times
By default, each expected method is configured to be called exactly once.
To be able to specify the number of expected calls, the generated expect_
methods return a MethodCallBuilder
, which amongst other methods contains a times
method.
The times
method accepts a usize value or a range. Here is the full list of supported values from the documentation:
Description | Type | Example |
---|---|---|
Exact amount of times | u64 |
3 |
Any amount of times | RangeFull |
.. |
At least | RangeFrom |
3.. |
At most (exclusive) | RangeTo |
..3 |
At most (inclusive) | RangeToInclusive |
..=3 |
Between (exclusive) | Range |
3..4 |
Between (inclusive) | RangeInclusive |
3..=4 |
#[cfg(test)]
use mockiato::mockable;
#[cfg_attr(test, mockable)]
trait Greeter {
fn greet(&self, name: &str);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn greet_the_world() {
let mut greeter = GreeterMock::new();
greeter
.expect_greet(|arg| arg.partial_eq("world"))
.times(1..2);
greeter.greet("world");
}
}
Specifying return values
The MethodCallBuilder
also provides a returns
method.
This returns
method allow you to specify a return value, which is returned when the corresponding mock method is invoked.
#[cfg(test)]
use mockiato::mockable;
#[cfg_attr(test, mockable)]
trait Greeter {
fn greet(&self, name: &str) -> String;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn greet_the_world() {
let mut greeter = GreeterMock::new();
greeter
.expect_greet(|arg| arg.partial_eq("world"))
.returns(String::from("Hello world"));
assert_eq!("Hello world", greeter.greet("world"));
}
}
Specifying different return values for the same call
For each method of the Greeter
trait a corresponding expect_<method_name>_calls_in_order
method is generated.
Ordered expectations can be used together with times
.
#[cfg(test)]
use mockiato::mockable;
#[cfg_attr(test, mockable)]
trait Greeter {
fn greet(&self, name: &str) -> String;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn greet_the_world() {
let mut greeter = GreeterMock::new();
greeter
.expect_greet(|arg| arg.partial_eq("world"))
.returns(String::from("Hello world"));
greeter
.expect_greet(|arg| arg.partial_eq("world"))
.returns(String::from("Hi world"));
greeter.expect_greet_calls_in_order();
assert_eq!("Hello world", greeter.greet("world"));
assert_eq!("Hi world", greeter.greet("world"));
}
}
Limitations
Feature | Description | Tracking Issue |
---|---|---|
Sync / Send |
Mocks are neither sync, nor send. | #106 |
Lifetimes on traits | Lifetimes are supported in the method signature, but not on the trait itself. | #117 |
Reference to generics | References to types containing a generic type are currently not supported due to a limitation in codegen involving lifetimes. | #123 |
Stable support | Mockiato requires nightly because we use the unstable proc_macro_diagnostics API to print helpful messages. Printing expected calls requires the unstable feature specialization . |
#161 |
Discussion
If you’d like to discuss or comment on this post, head over to reddit!