Crate faux[−][src]
Expand description
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 animpl
block into their mockable equivalentswhen!
: initializes a method stub by returning aWhen
. 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
Tools to check if an argument to a mocked method matches expectations.
Tools to stub the implementation or return value of your mocks.
Macros
Returns an ArgMatcher
that succeeds if the provided closure
returns true
.
Returns an ArgMatcher
that succeeds if the provided pattern
matches.
Structs
Provides methods to stub the implementation or return value of the mocked method.
Traits
Matcher for single argument of a method.