All the ways to mock your Rust code

8 points by drmorr


olliej

I can mock any of my code. Especially the older stuff.

More seriously I’m always concerned about how most devs seem to construct mocks (note: this doesn’t appear to be the case in this article). The general guidance devs get seems to be very shallow mocks - eg not real control flow, state transitions, etc. just the happy paths, little script ability, etc.

It’s one of those approaches that seems to cause tunnel vision and I’m unsure how folk address that (both for themselves, and during reviews for others)

e_terekhin

“What are you doing, you dumbass? The cost of a network request is orders of magnitude higher than the cost of a couple of pointer lookups. Just stick it in an Arc and call it a day.”

Great post and conclusion. I've seen 1), 2), 3), and 4) in practice many times, and for the moment I'm a method-1 guy hands down.

3) and 4) stop working entirely (without extra tricks) the moment the struct that needs to participate in tests is defined in one crate but has to be used from a test in another crate since #[cfg(test)] doesn't propagate cross-crate.

Between 1) and 2), I'm fairly convinced it's often correct to prefer Arc<dyn T> over <T> - not just in tests, but in general - because it keeps the code so much less complex structurally. Generics are great, they just shouldn't "spread" too far.

The one pattern resembling 6) I can think of, that tends to happen in my experience is using httpmock to test HTTP API clients.

Also one small nit: putting a trait bound directly on the struct definition is one of the things that encourages generic "spread": anything that mentions the struct now has to carry T: EventRecordable with it. The bound only really needs to live on the impl block:

struct SkEventRecorder<T> {
    recorder: T,
}

impl<T: EventRecordable> SkEventRecorder<T> { /* ... */ }

This is a fairly established Rust idiom - see e.g. rust-lang/api-guidelines#6.

e_terekhin

You have to use the async_trait crate, because you can’t have async traits in standard Rust. It’s fine, the crate is fine, it works well, it’s literally just an extra import and a proc macro annotation, but it annoys me that I have to use it.

was going to say that async_trait is no longer needed, since stable rust allows async methods in traits, but decided to check just in case.. and indeed this doesn't work since traits with -> impl Whatever in their methods are not dyn-compatible apparently.

dpc_pw

Note that dynamic dispatch is not slow. And the extra code generated by monorphisation is likely going to cause more performance problems due to i-cache pressure.

Dynamic dispatch is also correct w.r.t architecture and splitting crates, etc.

You should just use dynamic dispatch along the boundaries of architectural joint points.

Just use dynamic dispatch, trust me.

BTW. In theory there could be a proc-macro crate that makes the ceremony of definiting IThihg and type Thing = Arc<IThing>, etc. faster. But I actually never found a go-to one to use.