A library to create mocks out of structs

faux   mocks out of structs

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.

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"

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.

#[cfg_attr(test, faux::create)]
pub struct MyStructToMock { /* fields */ }

#[cfg_attr(test, faux::methods)]
impl MyStructToMock { /* methods to mock */ }

Examples

mod client {
    // #[faux::create] makes a struct mockable and
    // generates an associated `faux` function
    // e.g., `UserClient::faux()` will create a a mock `UserClient` 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,
}

#[derive(Debug, PartialEq)]
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)].

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 (but not external crates).

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 practice it seems to expand top-down (tested in Rust 1.42).

#[faux::create]
struct Foo { /*some items here */ }

#[faux::methods]
#[another_attribute]
impl Foo {
    /* some methods here */
}

In the snippet above, #[faux::methods] will expand first followed by #[another_attribute].faux is effectively ignoring the other macro and expanding based on the code you wrote.

If #[faux::methods] performs its expansion after another macro has modified the impl block, #[faux::methods] receives the expanded code. This code might contain different method signatures 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 fn run(&self, arg: Arg) -> Out {
    /* stuff inside */
}
fn run<'async>(&'async self, arg: Arg) -> Pin<Box<dyn std::future::Future<Output = Out> + Send + 'async>> {
    /* crazier stuff inside */
}

Because async-trait adds explicit lifetimes to the method signature, which faux cannot handle, having async-trait do its expansion first breaks faux. Note that even if faux could handle explicit lifetimes, our signature is now 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.

#[faux::methods]
#[async_trait]
impl MyStruct for MyTrait {
    async fn run(&self, arg: Arg) -> Out {
        /* stuff inside */
    }
}

If you find a proc macro that faux cannot handle, please open 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. Thus, faux does not rely on trait definitions for every mocked object, which would pollute their function signatures with either generics or trait objects. faux aims to create mocks out of user-defined structs, avoiding extra production code that exists solely for tests.

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.

Issues

Collection of the latest Issues

nrxus

nrxus

0

General issue for writing the guide

  • Exporting mocks across crates
  • Interactions with other proc macros (in particular, async-trait)
nrxus

nrxus

6

The only implemented matchers at the moment are:

Any: matches any argument Eq matches based on Arg: PartialEq EqAgainst matches based on Arg: PartialEq<Against>

We could implement other matchers so faux users don't have to handwrite their own matchers.

Quick ideas:

  • <
  • <=
  • >
  • >=
  • !
  • ||
  • &&
  • contains
  • ~custom closure. Unsure if this is all that much better than a just implementing ArgMatcher though.~

We can first decide what matchers to create and then see if when! can come up with an easy to use syntax for it.

cc: @muscovite

nrxus

nrxus

0

Add an argument matcher that is able to capture an argument for later verification.

Potential pitfalls:

  • It will probably require the argument to be cloneable + 'static. This is to help prevent the argument being dropped before the slot.
  • It will probably require the slot to be surrounded by some sort of Rc<RefCell<T>>. This will help prevent any soundness issues with the slot being dropped before the last call to the mocked method.

cc: @muscovite

nrxus

nrxus

0

faux should support verification that a mock got called with certain arguments.

cc: @muscovite

nrxus

nrxus

1

While there exist other libraries already capable of doing this, it might be nice to also provide that functionality here to not force the user to find yet another mocking framework on top of faux.

nrxus

nrxus

16

Neither of these work.

The issue is on the definition of the _when_{} method. The first two seem solvable together. Add all the generics from the mocked method to the when method. The third one seems like it would require "de-sugaring" the impl Foo into <T: Foo> for the when method only. The last one.... :man_shrugging: We could change it into a generic argument to the when method as a first-step MVP (read: hack).

nrxus

nrxus

3

aa18c66a349c8f33ad061e2d6ff655233aa2b762 allowed mocks to be Send/Sync by forcing the user only provide Send closures, and using Mutex instead of RefCell for the interior mutability of the MockStore.

While this is fine as an MVP to allow mocking structs in Send/Sync contexts, it is very much the opposite of an "zero cost abstraction" (TM). Now every user has to pay the cost of making sure their captured variables are always Send, and the performance implication of Mutex even when they do not care/need their mocks to be Send/Sync.

This is far from ideal.

Suggestion: An attribute to #[faux::create] and #[faux::methods] that specify if they need a sync/send. This can get annoying if you are always mocking sync/send structs (i.e., you are building an API in Rocket which forces the request guards to be Send/Sync and you want to mock the request guards). I think a feature turn in on crate-wide would reduce this pain.

nrxus

nrxus

2

#[faux::methods]creates a module with the name of the struct being mocked.

This means that if there are multiple impl blocks for the same type, two modules with the same name would be created in the same scope, hence collide, and fail to compile.

https://github.com/rust-lang/rust/issues/8995 would lift the requirement of making the dummy modules and fix this but I do not see an ETA in it being done soon.

Multiple impls within the same module is rare since we do not support generics nor trait impls but once we start supporting them this issue would become more important.

Multiple impls are allowed as long as there is no more than one inherent implementation and no more than one trait implementation per trait.

The user can get around this by wrapping impls in a separate module themselves, but forcing the user to write hacks to support this library would be very subpar UX.

A possible solution would be to mock an entire module but that is a bit far from being implemented,

nrxus

nrxus

0

Ideally something like this would work

Unfortunately custom inner attributes are not supported

I would also be okay with

But this currently returns a ModItem with no body.

The alternative is:

There is a clear ergonomic hit to this latter approach though. I would very much dislike foricng the user to write this kind of code just to support faux. This makes me unsure if a mod-level macro would be that useful until either option 1 or 2 are allowed.

Information - Updated Feb 09, 2022

Stars: 274
Forks: 9
Issues: 10

Repositories & Extras

Rust bindings for libinjection

Add libinjection to dependencies of Cargo

Rust bindings for libinjection

Rust bindings for the C++ api of PyTorch

LIghtweight wrapper for pytorch eg libtorch in rust

Rust bindings for the C++ api of PyTorch

Rust leveldb bindings

Almost-complete bindings for leveldb for Rust

Rust leveldb bindings

rust-analyzer is a modular compiler frontend for the Rust language

It also contains some tips &amp; tricks to help you be more productive when using rust-analyzer

rust-analyzer is a modular compiler frontend for the Rust language

Rust-Lightning is a Bitcoin Lightning library written in Rust

lightning, does not handle networking, persistence, or any other I/O

Rust-Lightning is a Bitcoin Lightning library written in Rust

Rust FUSE - Filesystem in Userspace

Rust library crate for easy implementation of Crate documentation

Rust FUSE - Filesystem in Userspace

Rust crate to implement a counterpart to the PBRT book's (3rd edition) C++ code:

Some images of the test scenes are shown below, but you can find more

Rust crate to implement a counterpart to the PBRT book's (3rd edition) C++ code:

A powerful mock object library for Rust

Mock objects are a powerful technique for unit testing software

A powerful mock object library for Rust
Http

375

HTTP mocking for Rust!

Before upgrading, make sure to check out the rustfmt as a general code style

HTTP mocking for Rust!
Http

329

HTTP mocking to test Rust applications

wiremock provides HTTP mocking to perform black-box testing of Rust applications that

HTTP mocking to test Rust applications
Http

289

HTTP mocking library for Rust

Advanced verification and debugging support

HTTP mocking library for Rust
Facebook Instagram Twitter GitHub Dribbble
Privacy