[−][src]Attribute Macro faux::methods
#[methods]
Transforms the given methods into mockable versions of themselves and provides a new method to mock them.
The generated methods look like
impl MyStruct { /* other methods before */ // I is a tuple of all the non-receiver arguments of #{method_name} // O is the output of #{method_name} _when_#{method_name}(&mut self) -> When<I,O> { /* auto generated code */ } }
These auto-generated methods can be called directly but a more ergonomic way is by using when!.
Associated functions and private methods are not mocked, and are instead proxied to the real implementation.
Requirements
#[create] must have been previously called for this struct.
Usage
#[faux::create] pub struct MyStruct { /* fields */ } #[faux::methods] impl MyStruct { pub fn new(data: Vec<u32>) -> Self { /* implementation code */ } pub fn get(&self) -> usize { 20 } } use std::io::{self, Read}; #[faux::methods] impl Read for MyStruct { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { /* potentially complicated 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 when mocking their methods let mut fake = MyStruct::faux(); faux::when!(fake.get).safe_then(|_| 3); assert_eq!(fake.get(), 3); // unsafe because a parameter is a reference. See When's documentation unsafe { faux::when!(fake.read).then(|a| Ok(a[0] as usize)) } assert_eq!(fake.read(&mut vec![10]).unwrap(), 10);
Attribute arguments
There are times when faux needs a little extra information to
properly mock your struct and its methods. Use path = "path::to::imported::mod"
when you are mocking methods from a
struct imported from a different module. Use self_type = "{self_type}"
when your method receivers would not allow faux to
get an owned value from it, this is particularly important for
self: Rc<Self>
and self: Arc<Self>
receivers.
path
Faux supports mocking structs from a different module as long as
we tell #[methods]
where we are importing the struct from.
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 from somewhere else use super::MyStruct; // so we have to tell faux where it came from #[faux::methods(path = "super")] impl MyStruct { pub fn four(&self) -> i32 { self.three() + 1 } } } } mod bar { // we are importing a module from somewhere else use crate::foo; // so we need to tell faux where that module came from #[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).safe_then(|_| 30); faux::when!(x.four).safe_then(|_| 40); faux::when!(x.five).safe_then(|_| 50); assert_eq!(x.three(), 30); assert_eq!(x.four(), 40); assert_eq!(x.five(), 50);
self_type
Allowed values:
#[methods(self_type = "Rc")]
#[methods(self_type = "Arc")]
#[methods(self_type = "Box")]
#[methods(self_type = "Owned")]
- this is the default and not necessary
The self_type
specified in methods must match the self_type
in create
and is required iff one
was specified there.
The method receivers for all the methods in the impl block must be
convertable from the self_type
specified. In particular, while a
&self
can be obtained from an Rc<Self>
or an Arc<Self>
, a
&mut self
cannot. This means that if you specify self_type = "Rc"
, then none of the methods being mocked may take a &mut self
as a receiver. If you believe that a certain combination of
specified self_type
and method receiver is doable but now
allowed in faux
please file an issue.
Another effect of specifying the self_type
is gaining the
ability to include methods and associated functions that return
Self
wrapped in that pointer type.
use std::rc::Rc; #[faux::create(self_type = "Rc")] pub struct ByRc {} #[faux::methods(self_type = "Rc")] impl ByRc { // you can still return plain Self pub fn new() -> Self { ByRc {} } // but you may also now return the self wrapped in the self_type pub fn new_rc() -> Rc<Self> { Rc::new(ByRc {}) } // can call methods with an Rc<Self> receiver type pub fn by_rc(self: Rc<Self>) {} // Rc<Self> derefs to &self so this is okay pub fn by_ref(&self) {} }
Panics
Non-mocked methods
Faux will not try to return a default value on non-mocked methods so it panics instead.
#[faux::create] pub struct MyStruct {} #[faux::methods] impl MyStruct { pub fn get(&self) -> usize { 50 } } let fake = MyStruct::faux(); // when!(fake.get).then_safe() was not invoked and thus the method was not mocked fake.get(); // <~ panics with "'MyStruct::get' is not mocked"
Mocking real instances
Spies are not supported and thus mocking real instances panic.
#[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 with "not allowed to mock a real instance!"
Structs with self: Rc<Self>
or self: Arc<Self>
methods that have been cloned
While you do not need to specify #[self_type = "Rc"]
or
#[self_type = "Arc"]
even if you have self: Rc<Self>
or self: Arc<Self>
receivers respectively, the real instances that are
created through this must be the only reference to the object
when calling these methods or else your test will fail.
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 */ } } // works if there is only a single reference let rcd = Rc::new(Owned::new()); rcd.by_rc(); // this works // panics if there are multiple references let rcd = Rc::new(Owned::new()); let clone = rcd.clone(); rcd.by_rc(); // this panics because there are now multiple references
In the case of a panic the panic message faux produces should
guide you towards using the self_type
argument in the faux
attributes
Known Limitations
#13: Only a single inherent impl block and a single trait implementation per trait per type may exist.
#14: Methods may not contain instances of the same struct as parameters.
Caveats
Methods/functions that return the mocked struct
Special care is taken for methods and function that return an
instance of the mocked struct. Unfortunately only methods that
return -> Self
or -> #{SomeStruct}
are
handled.
Methods/functions that returns your type wrapped as a generic of
another type (e.g., Result<Self, _>
) cannot be wrapped in a faux
impl. The exception to this is methods that receive an specified
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 outside the impl tagged
with #[faux::method]
and have it redirect to the method 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
faux
supports implementing types of the form path::to::Type
as
long as the path does not contain any super
or crate
keywords. To implement such types use the path
argument.