[][src]Attribute Macro faux::methods

#[methods]

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

The mockable methods can then be mocked 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

path

Indicates that the struct came from an imported module.

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

Indicates how the real value of the struct is wrapped when not being mocked, e.g., wrapped as an owned instance vs an Rc<>. faux will guide to use this attribute when needed through either a compile time error or a panic. Do not use unless faux asks you to.

If this attribute is set, the #[create] attribute must specify the same self_type in the struct.

By default faux assumes that it has access to an owned instance of the struct. However, the #[create] macro may have a self_type specified that wraps the instance differently. This is useful when the method receivers are all the same non-owned received (e.g., self: Rc<Self>).

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 not 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 return plain Self
    pub fn new() -> Self {
        ByRc {}
    }

    // or 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 too
    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.

This example panics
#[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 are not supported and thus mocking real instances panic.

This example panics
#[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

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.

This example panics
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(); // <~ panics: reference is not unique

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.

#18: Generic methods and impl return types are not allowed

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.

This example deliberately fails to compile
#[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.