Attribute Macro faux::methods[][src]

#[methods]

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-mocked methods

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

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

let fake = MyStruct::faux();
// fake.get is not mocked
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

  • #13: Within a module, for a single struct, only a single inherent impl and a single trait impl per trait may exist.
  • #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

Returning the mockable struct wrapped as a generic of another type (e.g., Option<Self>) is not currently supported. The exception to this is returning an instance wrapped by the self_type.

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

#[faux::methods]
impl MyStruct {
    pub fn try_to_new() -> Result<Self, String> {
        Ok(MyStruct {})
    }
}

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

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

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

// do not tag this one
impl MyStruct {
    pub fn try_to_new() -> Result<Self, String> {
        Ok(MyStruct::new())
    }
}

let x = MyStruct::try_to_new();
assert!(x.is_ok());

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.