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.
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:
toml
[dev-dependencies]
faux = "0.0.9"
faux provides two attributes:
* #[create]: transforms a struct into a mockable equivalent
* #[methods]: transforms the methods in an impl block into
Use Rust's #[cfg_attr(...)] to gate these attributes to the test
config only.
```rust
pub struct MyStructToMock { /* fields */ }
impl MyStructToMock { /* methods to mock */ } ```
``rust
mod client {
// #[faux::create] makes a struct mockable and
// generates an associatedfauxfunction
// e.g.,UserClient::faux()will create a a mockUserClient` instance
#[faux::create]
pub struct UserClient { /* data of the client */ }
#[derive(Clone)]
pub struct User {
pub name: String
}
// #[faux::methods ] makes every public method in the `impl` block mockable
#[faux::methods]
impl UserClient {
pub fn fetch(&self, id: usize) -> User {
// does some network calls that we rather not do in tests
User { name: "".into() }
}
}
}
use crate::client::UserClient;
pub struct Service { client: UserClient, }
pub struct UserData { pub id: usize, pub name: String, }
impl Service { fn user_data(&self) -> UserData { let id = 3; let user = self.client.fetch(id); UserData { id, name: user.name } } }
// A sample #[test] for Service that mocks the client::UserClient
fn main() {
// create a mock of client::UserClient using faux
let mut client = client::UserClient::faux();
// mock fetch but only if the argument is 3
// argument matchers are optional
faux::when!(client.fetch(3))
// stub the return value for this mock
.then_return(client::User { name: "my user name".into() });
// prepare the subject for your test using the mocked client
let subject = Service { client };
// assert that your subject returns the expected data
let expected = UserData { id: 3, name: String::from("my user name") };
assert_eq!(subject.user_data(), expected);
} ```
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)].
faux lets you mock the return value or implementation of:
self: Rc<Self>)faux also provides easy-to-use argument matchers.
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.
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).
```rust ignore
struct Foo { /*some items here */ }
impl Foo { /* some methods here */ } ```
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:
rust ignore
async fn run(&self, arg: Arg) -> Out {
/* stuff inside */
}
rust ignore
fn run<'async>(&'async self, arg: Arg) -> Pin<Box<dyn std::future::Future<Output = Out> + Send + 'async>> {
/* crazier stuff inside */
}
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.
```rust ignore
impl MyStruct for MyTrait { async fn run(&self, arg: Arg) -> Out { /* stuff inside */ } } ```
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 impls.
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.
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.
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.