Announcing `faux`: A traitless mocking library

Announcement Post
Source code
Crates.io

Hi all,

I have been working on this for a few months now and I finally got the nerve to release it. One of the things that I have been finding pretty hard to do in rust is testing of code that that has some "expensive" call, such as a network request. While I have used mockito to mock network requests, it always felt awkward to mock the requests at every layer of my tests since it made the more higher level pieces of my code know too much about what the lower level components (the ones doing the network requests) were doing. I created faux with the goal to solve this. Now I can still use mockito to mock the network requests in one layer of my tests only, and everything above that can mock only the layer it is directly using, instead of the network request.

I am super open to feedback as this is my first library that I am releasing to the wild so I am sure I have made mistakes along the way, either in the code or in how I wrote the docs.

I hope you all take a look and let me know what you think!

7 Likes

This is pretty awesome!

Here's a quick suggestion: rather than require #[cfg_attr(test, faux::create)], just use #[faux::create] and emit the #[cfg(any(test, rustdoc))] in your macro expansion.

I kept going back and forth on that, unsure if it would be too "implicit" and not enough control for the user.
But hearing someone else also suggest it is tipping me over, thanks!

I made an issue to make sure I won't forget it: emit #[cfg(any(test, rustdoc))] in the macro expansions · Issue #19 · nrxus/faux · GitHub

I like the API for this a lot. Are there any plans to support mocking of traits as well though? Or are you leaving that to the other crates that already exist?

It is not high in my priority list because other crates already exist for such use cases. I do want to support mocking implementations of traits relatively soon but not the traits themselves if that makes sense.

I want to support this:

#[faux::methods]
impl SomeTrait for SomeStruct { /* methods implementations */ }

sooner than this:

`#[faux::create]`
trait SomeTrait { /* trait definitions */  }

Unless for some reason faux conflicts with existing trait mocking libraries in which case I would work to either make them not conflict or have faux support it itself.

That said, both of those are theoretically simple to implement (of course, modulo all the real world issues):

#[faux::methods] impl Trait for Type is "just" hooking up the existing mocking setup to the trait methods. The only potential semantic issue is handling when names conflict between inherent and trait methods. (For them to have different semantics is a huge footgun anyway, though.)

#[faux::create] trait Trait is "just" creating an empty #[faux::create] struct Unnameable; that implements the trait (probably by panicking) and supports mocking the said methods. (I.e. it would be built on the above.) The one potential semantic issue is how to create the faux; Trait::faux() doesn't work without a tricky nonobvious trait impl on dyn Trait.

1 Like

That dyn Trait trick is wild, thanks for sharing that!

They are both definitely feasible but my preference would be to fill an existing gap first. Although I will definitely keep that in mind since it is for sure valid to want all your mocking needs within one library and less piecemeal.

Small correction: you can actually impl directly on dyn Trait (for some reason I thought you needed the trait impl). Also important(ish) to note: even though this is (nominally) an impl on dyn Trait, no dynamic dispatch is involved here.

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.