A mock injection strategy for rust

A while back I asked about a feature that would allow me to use my C++ mock injection strategy in rust. The answer was that Rust doesn't support that strategy. Since then I've worked out a mock injection strategy that satisfies me.

The basic idea is for classes that have dependencies to wrap around a generic parametrized with those dependencies. Testing the generic parametrized with mocks serve as tests for the class because there's no logic in the wrapper.

I'm interested in what people here think of it. Am I mistaken in my belief that making classes testable in this way has no performance cost relative to a naive implementation? My understanding is that people who use unit tests professionally expect to have a utility that generates mocks automatically rather than hand-rolling their own the way I have for my self-teaching project. Perhaps this strategy could be meaningfully automated?

2 Likes

This is indeed the way to mock in Rust, and I do it regularly. Because of monomorphization, production code should compile as if it were the same.

You don't need the indirection of HouseImplementation / House though. You can just use default type parameters:

struct House<S: ISink=Sink, D: IDishwasher=Dishwasher, R: IRefrigerator=Refrigerator> {
   sink: S,
   dishwasher: D,
   refrigerator: R
}

Outside of the house module, you can just call House::new and such and it will be inferred to be using the production types.

I strongly prefer to define traits inside the e.g. house module, which specify exactly which methods House needs from its dependencies, rather than to use mocks of the entire interface of the dependency. That way, these parameters also create a design constraint on the behavior of your dependencies that this type can be coupled to. Its even better when that interface can be reduced to a set of standard library traits. You also do this, but I wouldn't call these interfaces something like ISink (Sinkable is more idiomatic Rust naming convention, also), rather I'd name them after whatever House is using Sink for.

There is verbosity to this, and I don't know of an easy way it could be automated. I don't think it could be done trivially through compiler plugins because it requires transformations at a lot of positions in the house module.

2 Likes

There is verbosity to this, and I don't know of an easy way it could be
automated. I don't think it could be done trivially through compiler
plugins because it requires transformations at a lot of positions in the
house module.

I wouldn't want to try to make something that does all the work, but some of the simpler subtasks, e.g. "given trait Sinkable and struct Sink, generate impl Sinkable for Sink", or "given trait Sinkable, generate struct MockSink and impl Sinkable for MockSink supporting the features one generally expects from a mock" seem like plausible asks.

This is a great post. Looks like you're in the same mind set as myself. I have years of C++/C#, but looking to Rust for future projects. One thing that frustrates me with Rust, is there does not appear much in the way of alternative ways of doing things the Rust idiomatic way, as opposed to the C++ way, so I am left guessing as to the correct way to tackle a problem. Unit testing and writing testable code is a case in point. I'm currently working on a nursery project for the purposes of learning Rust, which requires use of the file system. My instinct is to use a virtual file system (an interface to interact with the filesystem, rather than using the file system api directly), but any code I write needs to be unit-testable, so your mock injection strategy would seem to fit the bill.

You may be interested in my projects — mockers, which generates mock object for trait.

1 Like

Nice, I'll take a look

In fairness, it's a young language. We don't truly know what the Rust way is yet.