Crate faux[][src]

A library to create mocks out of structs.

faux allows you to mock the methods of structs for testing without complicating or polluting your code.

Part of faux’s philosophy is that only visible behavior should be mocked. In practice, this means faux only mocks public methods. Fields are not mocked, as they are data, not behavior. Private methods are not mocked, as they are invisible to others.

At a high level, faux is split into:

  • #[create]: transforms a struct into a mockable equivalent
  • #[methods]: transforms the methods in an impl block into their mockable equivalents
  • when!: initializes a method stub by returning a When. Passing optional argument matchers restricts which arguments will invoke the mock.
  • When: lets you stub a mocked method’s return value or implementation

Getting Started

faux makes liberal use of unsafe Rust features, so it is only recommended for use inside tests. To prevent faux from leaking into your production code, set it as a dev-dependency in your Cargo.toml:

[dev-dependencies]
faux = "^0.1"

Examples

Simple

// restrict faux to tests by using `#[cfg_attr(test, ...)]`
// faux::create makes a struct mockable and generates an
// associated `faux()` function
// e.g.: `HttpClient::faux()` will create a mock `HttpClient` instance
#[cfg_attr(test, faux::create)]
pub struct HttpClient { /* */ }

// this is just a bag of data with no behavior
// so we do not attach `#[faux::create]`
#[derive(PartialEq, Clone, Debug)]
pub struct Headers {
    pub authorization: String,
}

// `faux::methods` makes every public method in the `impl` block mockable
#[cfg_attr(test, faux::methods)]
impl HttpClient {
    pub fn post(&self, path: &str, headers: &Headers) -> String {
        /* makes network calls that we'd rather not do in unit tests */
    }
}

#[cfg(test)]
#[test]
fn test() {
  // use the generated `faux()` function to create a mock instance
  let mut mock = HttpClient::faux();

  let headers = Headers { authorization: "Bearer foobar".to_string() };

  // use `faux::when!` to mock the behavior of your methods
  // you can specify arguments to match against when the mock is invoked
  faux::when!(
      // arguments are converted into argument matchers
      // the default argument matcher performs an equality check
      // use `_` to create a universal argument matcher
      // the argument matchers below specify to ignore the first argument
      // but that the second one must equal `headers`
      mock.post(_, headers.clone())
  )
  // mock the return value
  .then_return("{}".to_string());

  assert_eq!(mock.post("any/path/does/not/mater", &headers), "{}");
  assert_eq!(mock.post("as/i/said/does/not/matter", &headers), "{}");

  // if you want to mock all calls to a method, you can omit argument matchers
  faux::when!(mock.post).then_return("OK".to_string());
  let other_headers = Headers { authorization: "other-token".to_string() };
  assert_eq!(mock.post("other/path", &other_headers), "OK");
}

Mocking the same method multiple times

A single method can be mocked multiple times. When doing so, faux checks every mock for the method in a last-in-first-out fashion until it finds a mock whose argument matchers match the invocation arguments.

#[cfg(test)]
#[test]
fn test() {
  let mut mock = HttpClient::faux();
  let headers = Headers { authorization: "Bearer foobar".to_string() };
  let other_headers = Headers { authorization: "other-token".to_string() };

  // catch-all mock to return "OK"
  faux::when!(mock.post).then_return("OK".to_string());
  // mock for specific headers to return "{}"
  faux::when!(mock.post(_, headers.clone())).then_return("{}".to_string());

  assert_eq!(mock.post("some/path", &headers), "{}"); // matches specific mock
  assert_eq!(mock.post("some/path", &other_headers), "OK"); // matches catch-all mock
}

Mocking implementation

faux supports stubbing of not just the return value but also the implementation of a method. This is done using then().

#[cfg(test)]
#[test]
fn test() {
  let mut mock = HttpClient::faux();
  let headers = Headers { authorization: "Bearer foobar".to_string() };

  faux::when!(mock.post).then(|(path, _)| path.to_string().to_uppercase());
  assert_eq!(mock.post("another/path", &headers), "ANOTHER/PATH");
}

Mocking with non-static data

Let’s add a new method to our HttpClient that returns borrowed data. This cannot be mocked using safe code, so faux provides .then_unchecked() and .then_unchecked_return() to mock such methods.

#[cfg_attr(test, faux::methods)]
impl HttpClient {
    pub fn host(&self) -> &str {
        /* returns a reference to some internal data */
    }
}

#[cfg(test)]
#[test]
fn test() {
  let mut mock = HttpClient::faux();

  // `then_unchecked()` and `then_unchecked_return()` require unsafe
  // they allow mocking methods that return non-static values (e.g. references)
  // or to mock using non-static closures
  let ret = "some-value".to_string();
  unsafe { faux::when!(mock.host).then_unchecked_return(ret.as_str()) }
  assert_eq!(mock.host(), &ret);
}

Features

faux lets you mock the return value or implementation of:

  • Async methods
  • Trait methods
  • Generic struct methods
  • Methods with pointer self types (e.g., self: Rc<Self>)
  • Methods in external modules

faux also provides easy-to-use argument matchers.

Modules

matcher

Tools to check if an argument to a mocked method matches expectations.

when

Tools to stub the implementation or return value of your mocks.

Macros

from_fn

Returns an ArgMatcher that succeeds if the provided closure returns true.

pattern

Returns an ArgMatcher that succeeds if the provided pattern matches.

when

Creates a When instance to mock a specific method in a struct.

Structs

When

Provides methods to stub the implementation or return value of the mocked method.

Traits

ArgMatcher

Matcher for single argument of a method.

Attribute Macros

create

Transforms a struct into a mockable version of itself.

methods

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