Nothing Special   »   [go: up one dir, main page]

Crate mockall

source ·
Expand description

A powerful mock object library for Rust.

Mockall provides tools to create mock versions of almost any trait or struct. They can be used in unit tests as a stand-in for the real object.

Usage

There are two ways to use Mockall. The easiest is to use #[automock]. It can mock most traits, or structs that only have a single impl block. For things it can’t handle, there is mock!.

Whichever method is used, the basic idea is the same.

  • Create a mock struct. It’s name will be the same as the original, with “Mock” prepended.
  • In your test, instantiate the mock struct with its new or default method.
  • Set expectations on the mock struct. Each expectation can have required argument matchers, a required call count, and a required position in a Sequence. Each expectation must also have a return value.
  • Supply the mock object to the code that you’re testing. It will return the preprogrammed return values supplied in the previous step. Any accesses contrary to your expectations will cause a panic.

User Guide

Getting Started

use mockall::*;
use mockall::predicate::*;
#[automock]
trait MyTrait {
    fn foo(&self, x: u32) -> u32;
}

fn call_with_four(x: &MyTrait) -> u32 {
    x.foo(4)
}

let mut mock = MockMyTrait::new();
mock.expect_foo()
    .with(predicate::eq(4))
    .times(1)
    .returning(|x| x + 1);
assert_eq!(5, call_with_four(&mock));

Static Return values

Every expectation must have an associated return value (though when the nightly feature is enabled expectations will automatically return the default values of their return types, if their return types implement Default.). For methods that return a static value, the macros will generate an Expectation struct like this. There are two ways to set such an expectation’s return value: with a constant (return_const) or a closure (returning). A closure will take the method’s arguments by value.

#[automock]
trait MyTrait {
    fn foo(&self) -> u32;
    fn bar(&self, x: u32, y: u32) -> u32;
}

let mut mock = MockMyTrait::new();
mock.expect_foo()
    .return_const(42u32);
mock.expect_bar()
    .returning(|x, y| x + y);

Additionally, constants that aren’t Clone can be returned with the return_once method.

struct NonClone();
#[automock]
trait Foo {
    fn foo(&self) -> NonClone;
}

let mut mock = MockFoo::new();
let r = NonClone{};
mock.expect_foo()
    .return_once(move || r);

return_once can also be used for computing the return value with an FnOnce closure. This is useful for returning a non-Clone value and also triggering side effects at the same time.

fn do_something() {}

struct NonClone();

#[automock]
trait Foo {
    fn foo(&self) -> NonClone;
}

let mut mock = MockFoo::new();
let r = NonClone{};
mock.expect_foo()
    .return_once(move || {
        do_something();
        r
    });

Mock objects are always Send. If you need to use a return type that isn’t, you can use the return_const_st, returning_st, or return_once_st, methods. If you need to match arguments that are not Send, you can use the withf_st These take a non-Send object and add runtime access checks. The wrapped object will be Send, but accessing it from multiple threads will cause a runtime panic.

#[automock]
trait Foo {
    fn foo(&self, x: Rc<u32>) -> Rc<u32>;   // Rc<u32> isn't Send
}

let mut mock = MockFoo::new();
let x = Rc::new(5);
let argument = x.clone();
mock.expect_foo()
    .withf_st(move |x| *x == argument)
    .returning_st(move |_| Rc::new(42u32));
assert_eq!(42, *mock.foo(x));

Matching arguments

Optionally, expectations may have argument matchers set. A matcher will verify that the expectation was called with the expected arguments, or panic otherwise. A matcher is anything that implements the Predicate trait. For example:

#[automock]
trait Foo {
    fn foo(&self, x: u32);
}

let mut mock = MockFoo::new();
mock.expect_foo()
    .with(eq(42))
    .return_const(());

mock.foo(0);    // Panics!

See predicate for a list of Mockall’s builtin predicate functions. For convenience, withf is a shorthand for setting the commonly used function predicate. The arguments to the predicate function are the method’s arguments, by reference. For example:

#[automock]
trait Foo {
    fn foo(&self, x: u32, y: u32);
}

let mut mock = MockFoo::new();
mock.expect_foo()
    .withf(|x: &u32, y: &u32| x == y)
    .return_const(());

mock.foo(2 + 2, 5);    // Panics!

Matching multiple calls

Matchers can also be used to discriminate between different invocations of the same function. Used that way, they can provide different return values for different arguments. The way this works is that on a method call, all expectations set on a given method are evaluated in FIFO order. The first matching expectation is used. Only if none of the expectations match does Mockall panic. For example:

#[automock]
trait Foo {
    fn foo(&self, x: u32) -> u32;
}

let mut mock = MockFoo::new();
mock.expect_foo()
    .with(eq(5))
    .return_const(50u32);
mock.expect_foo()
    .with(eq(6))
    .return_const(60u32);

One common pattern is to use multiple expectations in order of decreasing specificity. The last expectation can provide a default or fallback value, and earlier ones can be more specific. For example:

#[automock]
trait Foo {
    fn open(&self, path: String) -> Option<u32>;
}

let mut mock = MockFoo::new();
mock.expect_open()
    .with(eq(String::from("something.txt")))
    .returning(|_| Some(5));
mock.expect_open()
    .return_const(None);

Call counts

By default, every expectation is allowed to be called an unlimited number of times. But Mockall can optionally verify that an expectation was called a fixed number of times, or any number of times within a given range.

#[automock]
trait Foo {
    fn foo(&self, x: u32);
}

let mut mock = MockFoo::new();
mock.expect_foo()
    .times(1)
    .return_const(());

mock.foo(0);    // Ok
mock.foo(1);    // Panics!

See also never and times.

Sequences

By default expectations may be matched in any order. But it’s possible to specify the order by using a Sequence. Any expectations may be added to the same sequence. They don’t even need to come from the same object.

# use mockall::*;
#[automock]
trait Foo {
    fn foo(&self);
}

# fn main() {
let mut seq = Sequence::new();

let mut mock1 = MockFoo::new();
mock1.expect_foo()
    .times(1)
    .in_sequence(&mut seq)
    .returning(|| ());

let mut mock2 = MockFoo::new();
mock2.expect_foo()
    .times(1)
    .in_sequence(&mut seq)
    .returning(|| ());

mock2.foo();    // Panics!  mock1.foo should've been called first.
# }

Checkpoints

Sometimes its useful to validate all expectations mid-test, throw them away, and add new ones. That’s what checkpoints do. Every mock object has a checkpoint method. When called, it will immediately validate all methods’ expectations. So any expectations that haven’t satisfied their call count will panic. Afterwards, those expectations will be cleared so you can add new expectations and keep testing.

#[automock]
trait Foo {
    fn foo(&self);
}

let mut mock = MockFoo::new();
mock.expect_foo()
    .times(2)
    .returning(|| ());

mock.foo();
mock.checkpoint();  // Panics!  foo hasn't yet been called twice.
#[automock]
trait Foo {
    fn foo(&self);
}

let mut mock = MockFoo::new();
mock.expect_foo()
    .times(1)
    .returning(|| ());

mock.foo();
mock.checkpoint();
mock.foo();         // Panics!  The expectation has been cleared.

Reference arguments

Mockall can mock methods with reference arguments, too. There’s one catch: the matcher Predicate will take reference arguments by value, not by reference.

#[automock]
trait Foo {
    fn foo(&self, x: &u32) -> u32;
}

let mut mock = MockFoo::new();
let e = mock.expect_foo()
    // Note that x is a &u32, not a &&u32
    .withf(|x: &u32| *x == 5)
    .returning(|x: &u32| *x + 1);

assert_eq!(6, mock.foo(&5));

Reference return values

Mockall can also use reference return values. There is one restriction: the lifetime of the returned reference must be either the same as the lifetime of the mock object, or 'static.

Mockall creates different expectation types for methods that return references. Their API is the same as the basic Expectation, except for setting return values.

Methods that return 'static references work just like methods that return any other 'static value.

struct Thing(u32);

#[automock]
trait Container {
    fn get(&self, i: u32) -> &'static Thing;
}

const THING: Thing = Thing(42);
let mut mock = MockContainer::new();
mock.expect_get()
    .return_const(&THING);

assert_eq!(42, mock.get(0).0);

Methods that take a &self argument use an Expectation class like this, which gets its return value from the return_const method.

struct Thing(u32);

#[automock]
trait Container {
    fn get(&self, i: u32) -> &Thing;
}

let thing = Thing(42);
let mut mock = MockContainer::new();
mock.expect_get()
    .return_const(thing);

assert_eq!(42, mock.get(0).0);

Methods that take a &mut self argument use an Expectation class like this, class, regardless of whether the return value is actually mutable. They can take their return value either from the return_var or returning methods.

struct Thing(u32);

#[automock]
trait Container {
    fn get_mut(&mut self, i: u32) -> &mut Thing;
}

let thing = Thing(42);
let mut mock = MockContainer::new();
mock.expect_get_mut()
    .return_var(thing);

mock.get_mut(0).0 = 43;
assert_eq!(43, mock.get_mut(0).0);

Unsized types that are common targets for Deref are special. Mockall will automatically use the type’s owned form for the Expectation. Currently, the CStr, OsStr, Path, Slice, and str types are supported. Using this feature is automatic:

#[automock]
trait Foo {
    fn name(&self) -> &str;
}

let mut mock = MockFoo::new();
mock.expect_name().return_const("abcd".to_owned());
assert_eq!("abcd", mock.name());

Similarly, Mockall will use a Boxed trait object for the Expectation of methods that return references to trait objects.

#[automock]
trait Foo {
    fn name(&self) -> &dyn Display;
}

let mut mock = MockFoo::new();
mock.expect_name().return_const(Box::new("abcd"));
assert_eq!("abcd", format!("{}", mock.name()));

Impl Trait

Rust 1.26.0 introduced the impl Trait feature. It allows functions to return concrete but unnamed types (and, less usefully, to take them as arguments). It’s almost the same as Box<dyn Trait> but without the extra allocation. Mockall supports deriving mocks for methods that return impl Trait, with limitations. When you derive the mock for such a method, Mockall internally transforms the Expectation’s return type to Box<dyn Trait>, without changing the mock method’s signature. So you can use it like this:

struct Foo {}
#[automock]
impl Foo {
    fn foo(&self) -> impl Debug {
        // ...
    }
}

let mut mock = MockFoo::new();
mock.expect_foo()
    .returning(|| Box::new(String::from("Hello, World!")));
println!("{:?}", mock.foo());

However, impl Trait isn’t exactly equivalent to Box<dyn Trait> but with fewer allocations. There are some things the former can do but the latter can’t. For one thing, you can’t build a trait object out of a Sized trait. So this won’t work:

struct Foo {}
#[automock]
impl Foo {
    fn foo(&self) -> impl Clone {
        // ...
    }
}

Nor can you create a trait object that implements two or more non-auto types. So this won’t work either:

struct Foo {}
#[automock]
impl Foo {
    fn foo(&self) -> impl Debug + Display {
        // ...
    }
}

For such cases, there is no magic bullet. The best way to mock methods like those would be to refactor them to return named (but possibly opaque) types instead.

See Also impl-trait-for-returning-complex-types-with-ease.html

impl Future

Rust 1.36.0 added the Future trait. Unlike virtually every trait that preceeded it, Box<dyn Future> is mostly useless. Instead, you usually need a Pin<Box<dyn Future>>. So that’s what Mockall will do when you mock a method returning impl Future or the related impl Stream. Just remember to use pin in your expectations, like this:

struct Foo {}
#[automock]
impl Foo {
    fn foo(&self) -> impl Future<Output=i32> {
        // ...
    }
}

let mut mock = MockFoo::new();
mock.expect_foo()
    .returning(|| Box::pin(future::ready(42)));

Mocking structs

Mockall mocks structs as well as traits. The problem here is a namespace problem: it’s hard to supply the mock object to your code under test, because it has a different name. The solution is to alter import paths during test. The easiest way to do that is with the mockall_double crate.

#[automock] works for structs that have a single impl block:

use mockall_double::double;
mod thing {
    use mockall::automock;
    pub struct Thing{}
    #[automock]
    impl Thing {
        pub fn foo(&self) -> u32 {
            // ...
        }
    }
}

#[double]
use thing::Thing;

fn do_stuff(thing: &Thing) -> u32 {
    thing.foo()
}

#[cfg(test)]
mod t {
    use super::*;

    #[test]
    fn test_foo() {
        let mut mock = Thing::default();
        mock.expect_foo().returning(|| 42);
        do_stuff(&mock);
    }
}

For structs with more than one impl block or that have unsupported #[derive(X)] attributes, e.g. Clone, see mock! instead.

Generic methods

Generic methods can be mocked, too. Effectively each generic method is an infinite set of regular methods, and each of those works just like any other regular method. The expect_* method is generic, too, and usually must be called with a turbofish. The only restrictions on mocking generic methods are that all generic parameters must be 'static, and generic lifetime parameters are not allowed.

#[automock]
trait Foo {
    fn foo<T: 'static>(&self, t: T) -> i32;
}

let mut mock = MockFoo::new();
mock.expect_foo::<i16>()
    .returning(|t| i32::from(t));
mock.expect_foo::<i8>()
    .returning(|t| -i32::from(t));

assert_eq!(5, mock.foo(5i16));
assert_eq!(-5, mock.foo(5i8));

Methods with generic lifetimes

A method with a lifetime parameter is technically a generic method, but Mockall treats it like a non-generic method that must work for all possible lifetimes. Mocking such a method is similar to mocking a non-generic method, with a few additional restrictions. One restriction is that you can’t match calls with with, you must use withf instead. Another is that the generic lifetime may not appear as part of the return type. Finally, no method may have both generic lifetime parameters and generic type parameters.

struct X<'a>(&'a i32);

#[automock]
trait Foo {
    fn foo<'a>(&self, x: X<'a>) -> i32;
}

let mut mock = MockFoo::new();
mock.expect_foo()
    .withf(|f| *f.0 == 5)
    .return_const(42);
let x = X(&5);
assert_eq!(42, mock.foo(x));

Generic traits and structs

Mocking generic structs and generic traits is not a problem. The mock struct will be generic, too. The same restrictions apply as with mocking generic methods: each generic parameter must be 'static, and generic lifetime parameters are not allowed.

#[automock]
trait Foo<T: 'static> {
    fn foo(&self, t: T) -> i32;
}

let mut mock = MockFoo::<i16>::new();
mock.expect_foo()
    .returning(|t| i32::from(t));
assert_eq!(5, mock.foo(5i16));

Associated types

Traits with associated types can be mocked too. Unlike generic traits, the mock struct will not be generic. Instead, you must specify the associated types when defining the mock struct. They’re specified as metaitems to the #[automock] attribute.

#[automock(type Key=u16; type Value=i32;)]
pub trait A {
    type Key;
    type Value;
    fn foo(&self, k: Self::Key) -> Self::Value;
}

let mut mock = MockA::new();
mock.expect_foo()
    .returning(|x: u16| i32::from(x));
assert_eq!(4, mock.foo(4));

Multiple and inherited traits

Creating a mock struct that implements multiple traits, whether inherited or not, requires using the mock! macro. But once created, using it is just the same as using any other mock object:

pub trait A {
    fn foo(&self);
}

pub trait B: A {
    fn bar(&self);
}

mock! {
    // Structure to mock
    C {}
    // First trait to implement on C
    impl A for C {
        fn foo(&self);
    }
    // Second trait to implement on C
    impl B for C {
        fn bar(&self);
    }
}
let mut mock = MockC::new();
mock.expect_foo().returning(|| ());
mock.expect_bar().returning(|| ());
mock.foo();
mock.bar();

External traits

Mockall can mock traits and structs defined in external crates that are beyond your control, but you must use mock! instead of #[automock]. Mock an external trait like this:

mock! {
    MyStruct {}     // Name of the mock struct, less the "Mock" prefix
    impl Clone for MyStruct {   // specification of the trait to mock
        fn clone(&self) -> Self;
    }
}

let mut mock1 = MockMyStruct::new();
let mock2 = MockMyStruct::new();
mock1.expect_clone()
    .return_once(move || mock2);
let cloned = mock1.clone();

Static methods

Mockall can also mock static methods. But be careful! The expectations are global. If you want to use a static method in multiple tests, you must provide your own synchronization. See the synchronization example for a basic implementation. For ordinary methods, expectations are set on the mock object. But static methods don’t have any mock object. Instead, you must create a Context object just to set their expectations.

#[automock]
pub trait A {
    fn foo() -> u32;
}

let ctx = MockA::foo_context();
ctx.expect().returning(|| 99);
assert_eq!(99, MockA::foo());

A common pattern is mocking a trait with a constructor method. In this case, you can easily set the mock constructor method to return a mock object.

struct Foo{}
#[automock]
impl Foo {
    fn from_i32(x: i32) -> Self {
        // ...
    }
    fn foo(&self) -> i32 {
        // ...
    }
}

let ctx = MockFoo::from_i32_context();
ctx.expect()
    .returning(|x| {
        let mut mock = MockFoo::default();
        mock.expect_foo()
            .return_const(x);
        mock
    });
let foo = MockFoo::from_i32(42);
assert_eq!(42, foo.foo());

Generic static methods

Mocking static methods of generic structs or traits, whether or not the methods themselves are generic, should work seamlessly.

#[automock]
trait Foo<T: 'static> {
    fn new(t: T) -> MockFoo<T>;
}

let ctx = MockFoo::<u32>::new_context();
ctx.expect()
    .returning(|_| MockFoo::default());
let mock = MockFoo::<u32>::new(42u32);

Context checkpoints

The context object cleans up all expectations when it leaves scope. It also has a checkpoint method that functions just like a mock object’s checkpoint method.

#[automock]
pub trait A {
    fn foo() -> u32;
}

let ctx = MockA::foo_context();
ctx.expect()
    .times(1)
    .returning(|| 99);
ctx.checkpoint();   // Panics!

A mock object’s checkpoint method does not checkpoint static methods. This behavior is useful when using multiple mock objects at once. For example:

#[automock]
pub trait A {
    fn build() -> Self;
    fn bar(&self) -> i32;
}

let ctx = MockA::build_context();
ctx.expect()
    .times(2)
    .returning(|| MockA::default());
let mut mock0 = MockA::build();
mock0.expect_bar().return_const(4);
mock0.bar();
mock0.checkpoint();     // Does not checkpoint the build method
let mock1 = MockA::build();

One more thing: Mockall normally creates a zero-argument new method for every mock struct. But it won’t do that when mocking a struct that already has a method named new. The default method will still be present.

Modules

In addition to mocking types, Mockall can also derive mocks for entire modules of Rust functions. Mockall will generate a new module named “mock_xxx”, if “xxx” is the original module’s name. You can also use #[double] to selectively import the mock module.

Be careful! Module functions are static and so have the same caveats as static methods described above.

mod outer {
    use mockall::automock;
    #[automock()]
    pub(super) mod inner {
        pub fn bar(x: u32) -> i64 {
            // ...
        }
    }
}

#[double]
use outer::inner;

#[cfg(test)]
mod t {
    use super::*;

    #[test]
    fn test_foo_bar() {
        let ctx = inner::bar_context();
        ctx.expect()
            .returning(|x| i64::from(x + 1));
        assert_eq!(5, inner::bar(4));
    }
}

Foreign functions

One reason to mock modules is when working with foreign functions. Modules may contain foreign functions, even though structs and traits may not. Like static methods, the expectations are global.

mod outer {
    #[automock]
    pub mod ffi {
        extern "C" {
            pub fn foo(x: u32) -> i64;
        }
    }
}

#[double]
use outer::ffi;

fn do_stuff() -> i64 {
    unsafe{ ffi::foo(42) }
}

#[cfg(test)]
mod t {
    use super::*;

    #[test]
    fn test_foo() {
        let ctx = ffi::foo_context();
        ctx.expect()
            .returning(|x| i64::from(x + 1));
        assert_eq!(43, do_stuff());
    }
}

Debug

#[automock] will automatically generate Debug impls when mocking traits and struct impls. mock! will too, if you add a #[derive(Debug)], like this:

mock! {
    #[derive(Debug)]
    pub Foo {}
}

Async Traits

Async traits aren’t yet (as of 1.47.0) a part of the Rust language. But they’re available from the async_trait crate. Mockall is compatible with this crate, with two important limitations:

  • The #[automock] attribute must appear before the #[async_trait] attribute.

  • The #[async_trait] macro must be imported with its canonical name.

// async_trait works with both #[automock]
#[automock]
#[async_trait]
pub trait Foo {
   async fn foo(&self) -> u32;
}
// and mock!
mock! {
    pub Bar {}
    #[async_trait]
    impl Foo for Bar {
        async fn foo(&self) -> u32;
    }
}

Crate features

Mockall has a nightly feature. Currently this feature has two effects:

  • The compiler will produce better error messages.

  • Expectations for methods whose return type implements Default needn’t have their return values explicitly set. Instead, they will automatically return the default value.

With nightly enabled, you can omit the return value like this:

#[automock]
trait Foo {
    fn foo(&self) -> Vec<u32>;
}

let mut mock = MockFoo::new();
mock.expect_foo();
assert!(mock.foo().is_empty());

Examples

For additional examples of Mockall in action, including detailed documentation on the autogenerated methods, see examples.

Modules

Macros

  • Manually mock a structure.

Structs

  • Used to enforce that mock calls must happen in the sequence specified.

Traits

Attribute Macros

  • Automatically generate mock types for structs and traits.