faux
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.
See the API docs for more information.
faux is in its early alpha stages, so there are no guarantees of API stability.
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
:
[]
= "0.0.9"
faux
provides two attributes:
#[create]
: transforms a struct into a mockable equivalent#[methods]
: transforms the methods in animpl
block into
Use Rust's #[cfg_attr(...)]
to gate these attributes to the test
config only.
Examples
use crate UserClient;
// A sample #[test] for Service that mocks the client::UserClient
Due to constraints with rustdocs, the above example tests in
main()
rather than a #[test]
function. In real life, the faux
attributes should be gated to #[cfg(test)]
.
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.
Interactions With Other Proc Macros
While faux
makes no guarantees that it will work with other macro
libraries, it should "just" work. There are some caveats, however. For
a quick solution try making the faux
attributes (e.g.,
#[faux::methods]
) the first attribute.
Explanation
If another proc-macro
modifies the signature of a method before
faux
does its macro expansion, then it could modify the signature
into something not supported by faux
. Unfortunately, the order of
proc macros is not specified. However, in practive it seems to
expand top-down (tested in Rust 1.42).
In the snippet above, #[faux::methods]
will expand first followed by
#[another_attribute]
.
If faux
does its expansion first then faux
will effectively ignore
the other macro and expand based on the code that the user wrote. If
you want faux
to treat the code in the impl
block (or the
struct
) as-is, before the expansion then put it on the top.
If faux
does its expansion after, then faux
will use the code as
expanded by the first attribute. This might have a different signature
than what you originally wrote. Note that the other proc macro's
expansion may create code that faux
cannot handle (e.g., explicit
lifetimes).
For a concrete example, let's look at
async-trait
. async-trait
effectively converts:
async
Because async-trait
modifies the signature of the function to a
signature that faux
cannot handle (explicit lifetimes), having
async-trait
do its expansion before faux
would make faux
not
work. Note that even if faux
could handle explicit lifetimes, our
signature now it's so unwieldy that it would make mocks hard to work
with. Because async-trait
just wants an async
function signature,
and faux
does not modify function signatures, it is okay for faux
to expand first.
Since no expansions came before, faux
sees an async
function,
which it supports. faux
does its magic assuming this is a normal
async
function, and then async-trait
does its magic to convert the
signature to something that can work on trait impl
s.
If you find a procedural macro that faux
cannot handle please submit
an issue to see if faux
is doing something unexpected that conflicts
with that macro.
Goal
faux was founded on the belief that traits with single implementations are an undue burden and an unnecessary layer of abstraction. It aims to create mocks out of user-defined structs, avoiding extra production code that exists solely for tests. In particular, faux does not rely on trait definitions for every mocked object, which would pollute their function signatures with either generics or trait objects.
Inspiration
This library was inspired by mocktopus, a mocking library for
nightly Rust that lets you mock any function. Unlike mocktopus, faux
works on stable Rust and deliberately only allows for mocking public
methods in structs.