Names of traits that enable mocks

I can distinguish two types of trait, at least for the purpose of naming them in the application I'm developing.

  • A role-named trait is named for what it does and may have several implementors. For example, trait Printable might have a function print() that is implemented by diverse structs such Invitation and Order. struct PrintScheduler has something like pub fn queue(&mut self, to_be_printed: Arc<Mutex<dyn Printable>>) to queue things to to be printed in due course.
  • A service interface trait abstracts a single concrete struct so it can be injected and mocked. I've not yet been able to find a Rust-idiomatic way to name such traits. So let's explore one of many real examples from my application.

Tuner is a struct that receives microtuning specifications in a given format, and converts each microtuning into a set of MIDI messages that are then sent to a synthesizer of a particular range of models. From the MIDI messages, the synthesizer's firmware configures its tuning to conform with the required microtuning. To facilitate testing, actually sending the MIDI messages is decoupled from Tuner . We need:

  • a struct that can actually send MIDI messages (the production struct);
  • a struct that sends no MIDI messages but allows tests to confirm that Tuner will send them correctly (the mock struct);
  • a trait that allows either struct to be injected into Tuner .

What would you call the two MIDI sender structs and their common trait? The mock struct is the easy one: MockMidiSender. But what about the the production struct and the trait? There are several options.

  • trait MidiSender with something like struct MidiSenderImpl or struct RealMidiSender. Yet, as the production struct is what actually sends MIDI, we should not have to qualify its name. And I understand that Rust naming guidelines discourage Impl suffixes for structs.
  • struct MidiSender with something like trait MidiSenderTrait or trait MidiSenderBase. It's a trait, so it should not need a Trait suffix. And Base sounds like a superclass in object-oriented programming languages, which it not what is going on here.
  • struct MidiSender with trait TMidiSender. I like that. However, T in Rust is a common type parameter, so perhaps it would be better to avoid it in this context.
  • struct MidiSender with trait IMidiSender, where I stands for interface and is the standard prefix for interface names, where interface is the equivalent of trait in .NET. This is what I've chosen as the standard for service interface traits in my application. But many Rust developers will understandably not like borrowing from a rather different development technology. I'm an experienced C#/.NET developer and am quite new to Rust.

What would you do?

While not answering your questions directly...

I totally understand, your examples are artificially, but I would like to advice you to drop the OO-style of solving problems because I think the problems you're trying to solve are originating from "OO tactics".

My suggestion is to use traits like some sort of "last resort" and first look out for solutions using concrete types.

For the printing example, instead struct PrintScheduler accepting trait Printable it should accept something like a struct PrintJob or whatever: a concrete type, maybe even a POD like data, or a Vec with print commands etc.

Then printing clients provide methods like print_document(&self) -> PrintJob, and print_overview(&self) -> PrintJob etc.

For testing, you can simple check the generated PrintJob of some clients match your expectation.

Same thing for the PrintScheduler. It accepts PrintJob as inputs and generates some output, whatever the role of the concrete PrintScheduler is. The generated output of processed PrintJobs are also checked against your expectation etc.

In summary: pass around concrete data: a call-site ask object A to generate some data (struct), takes this data and feeds this data into object B. That's how things fits nice together. There is no need, that A implements some interface (trait) that B uses to call back on A.

Shuffle concrete data around, and things become simple(r).

Thanks, @mroth, that is useful advice. The IMidiSender example is real. It's the best candidate of all the service interface traits in my application for refactoring along the lines you suggest. The current pattern has caused testing complexity for mocking that particular trait. I should be able to reduce that complexity. So I shall give it a go.

I suspect I will still need a MockMidiSender and consequently an IMidiSender after the refactor. The IMidiSender just won't be injected into Tuner. And it's unlikely to be practical to remove all the service interface traits from the application. So, if anyone has any suggestions, I would still be very interested in an answer to my original question.

I have simplified the code with a refactor following @mroth's advice. Thanks! Of course the project still has mocks. Except where mocks are generated with mockall rather than being created from scratch, the development of mocks in any project will present the same naming problem. So I would be interested to hear what others have done.

If you've already followed @mroth's advice above and now have something like:

pub struct MidiData {
    ...
}

pub struct MicroTuning {
    ...
}

/// We need a runtime struct that stores tunings & connection, for various reasons
pub struct Tuner {
    tunings: Vec<MicroTuning>,
    synth: SynthConnection
    ...
}

impl Tuner {
    pub fn new(tunings: impl Iterator<Item = MicroTuning>, ...) -> Self {
        ...
    }

    pub fn send(&self) -> Result<()> {
        ...
    }
}

and you still want a way to mock out e.g. the generation of the MIDI message. I'd combine with

// could be pub(crate)?
pub trait ParseTunings {
    // this could probably be a provided method with a default impl (?)
    fn parse(&self, ...) -> MidiData {
        ...
    }
}

Personally, I'd probably still try avoid that by turning the whole thing even more on it's head and having something like the following (depending on the reasons for needing a runtime generated struct Tuner)

pub struct MidiData {
    ...
}

pub struct MicroTuning {
    ...
}

// there are probably much better options than a pub type alias
pub type MicroTunings = Vec<MicroTuning>

impl From<MicroTunings> for MidiData {
    fn from ...
}

pub struct SynthConnection {
    ...
}

impl SynthConnection {
    pub fn send(MidiData) -> Result<()>
}

Thanks for your suggestions for my application's code structure, @MusicalNinjaDad. Some of this is already in the application. You (or anyone else) would be most welcome to do a code review: PitchGrid-Continuum Bridge. I suggest starting with the Tuner struct in tuner.rs. That's perhaps the core of the application. Then work your way out from there for as far as you care to.

For context, the application is essentially finished. I've been held up releasing it pending fixes to bugs in the target synth's firmware. Those bugs prevent operation of some of the application's features. While I've been waiting, I've been intermittently polishing the application with refactors and more tests.