Attribute Macro faux::methods

source ·
#[methods]
Expand description

Transforms methods in an impl block into mockable versions of themselves.

Mockable methods can be mocked using when!.

Associated functions and private methods cannot be mocked. Calls to them are proxied to the real implementation.

Requirements

The struct definition must have been tagged with #[create].

Examples

// #[faux::create] is a pre-req of #[faux::methods]
#[cfg_attr(test, faux::create)]
pub struct MyStruct {
    /* fields */
}

// make every public method mockable
#[cfg_attr(test, faux::methods)]
impl MyStruct {
    pub fn new(data: Vec<u32>) -> Self {
        /* implementation code */
    }

    pub fn get(&self) -> usize {
        20
    }
}

use std::io::{self, Read};

// make every method mockable
#[cfg_attr(test, faux::methods)]
impl Read for MyStruct {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        /* implementation code */
    }
}

// #[methods] will not mock associated functions
// thus allowing you to still create real instances
let real = MyStruct::new(vec![5]);
assert_eq!(real.get(), 20);

// mock instances need to be mutable
let mut fake = MyStruct::faux();
faux::when!(fake.get).then_return(3);
assert_eq!(fake.get(), 3);

faux::when!(fake.read).then(|a| Ok(a[0] as usize));
assert_eq!(fake.read(&mut vec![10]).unwrap(), 10);

Attribute arguments

path

Indicates that the struct came from an imported module.

Examples

mod foo {
    #[faux::create]
    pub struct MyStruct {}

    // no need to tell #[faux::methods] where to find `MyStruct`
    // if defined in the same module
    #[faux::methods]
    impl MyStruct {
        pub fn three(&self) -> i32 {
            3
        }
    }

    mod foo_inner {
        // the type is being imported
        // so we have to tell faux where it came from
        use super::MyStruct;

        #[faux::methods(path = "super")]
        impl MyStruct {
            pub fn four(&self) -> i32 {
                self.three() + 1
            }
        }
    }
}

mod bar {
    // the module is being imported
    // so we have to tell faux where it came from
    use crate::foo;

    #[faux::methods(path = "crate")]
    impl foo::MyStruct {
        pub fn five(&self) -> i32 {
            self.three() + 2
        }
    }
}

let mut x = foo::MyStruct::faux();
faux::when!(x.three).then_return(30);
faux::when!(x.four).then_return(40);
faux::when!(x.five).then_return(50);

assert_eq!(x.three(), 30);
assert_eq!(x.four(), 40);
assert_eq!(x.five(), 50);

self_type

Tells the attribute how real instances of the mockable struct are being stored by #[create]. Read the docs in create for more information.

Because this argument specifies how the struct is stored, every mockable method must use a receiver that can be obtained from the specified self_type. For example, if self_type = Rc, then a &self or an self: Rc<Self> can be used as receivers, but a &mut self cannot. If you need an unsupported combination, please file an issue.

Examples

use std::rc::Rc;

#[faux::create(self_type = "Rc")]
pub struct ByRc {}

#[faux::methods(self_type = "Rc")]
impl ByRc {
    // you can return an owned instance
    pub fn new() -> Self {
        ByRc {}
    }

    // or an instance wrapped in the `self_type`
    pub fn new_rc() -> Rc<Self> {
        Rc::new(ByRc {})
    }

    // methods with an Rc<Self> receiver type can be called
    pub fn by_rc(self: Rc<Self>) {}

    // Rc<Self> derefs to &self, so this also works
    pub fn by_ref(&self) {}
}

Allowed values:

  • #[methods(self_type = "Owned")] (default)
  • #[methods(self_type = "Rc")]
  • #[methods(self_type = "Arc")]
  • #[methods(self_type = "Box")]

Note that methods with a self: Pin<...> receiver are mockable. self_type does not specify what types of receivers can be mocked, but how faux stores the instances internally.

Panics

Non-stubbed methods

#[faux::create]
pub struct MyStruct {}

#[faux::methods]
impl MyStruct {
    pub fn get(&self) -> usize {
        50
    }
}

let fake = MyStruct::faux();
// fake.get is not stubbed
fake.get(); // <~ panics

Mocking real instances

Spies (real instances with mocked methods) are not supported.

#[faux::create]
pub struct MyStruct {}

#[faux::methods]
impl MyStruct {
    pub fn new() -> MyStruct {
        MyStruct {}
    }

    pub fn get(&self) -> usize {
        50
    }
}

let mut fake = MyStruct::new();
faux::when!(fake.get); // <~ panics

Unsupported receiver/self_type combinations

If a mockable method uses self: Rc<Self> as a receiver and the self_type is not Rc, faux performs a uniqueness check to prevent unsound behavior. This also applies to self: Arc<Self>.

In the case of a panic, the message faux produces guides you towards using the self_type argument in the faux attributes.

use std::rc::Rc;

#[faux::create]
pub struct Owned { /* fields */ }

#[faux::methods]
impl Owned {
   pub fn new() -> Owned {
       /* implementation */
   }

   pub fn by_rc(self: Rc<Self>) {
       /* implementation */
   }
}

let rcd = Rc::new(Owned::new());
// this works because this is the only Rc for the instance
rcd.by_rc();

let rcd = Rc::new(Owned::new());
// clone the Rc so the uniqueness check fails
let clone = rcd.clone();
rcd.by_rc(); // <~ panics: reference is not unique

Known Limitations

  • #14: Methods cannot have arguments of the same type as their struct.
  • #18: Generic methods and impl return types are not supported.

Caveats

Returning mockable struct

When referring to the mockable struct in the signature (either by name or by Self) only special cases are allowed. In particular, it is only allowed in the return position of the signature for the follow cases:

  • Returning the struct itself (e.g., fn new() -> Self)

  • Returning the struct wrapped directly in: Rc, Arc, Box, Result, or Option. For Result, referring to the struct is only allowed if it’s the Ok variant of the result. (e.g., fn load() -> Result<Self, Error>)

Any other kind of return type that refers to the mocked struct is not supported by faux. Please file an issue if you have a use case that you believe should be common enough for faux to handle automatically.

A workaround is to place the functions in an untagged impl block and have them call methods inside the tagged impl.

pub enum Either<X, Y> {
    Left(X),
    Right(Y),
}

#[faux::create]
pub struct MyStruct {
    x: i32
}

#[faux::methods]
impl MyStruct {
    fn new() -> Self {
        MyStruct { x: 4 }
    }
}

// do not tag this one
impl MyStruct {
    pub fn make_either() -> Either<Self, String> {
        Either::Left(MyStruct::new())
    }
}

let x = MyStruct::make_either();
assert!(matches!(x, Either::Left(MyStruct { .. })));

Paths in types

#[methods] can be added to blocks of the form impl path::to::Type as long as the path does not contain the super or crate keywords. If it does, use the path argument to explicitly specify the path.