I have a bunch of functions I want to test that take one or more channels.
The channel behavior is either irrelevant for unit testing or being tested
elsewhere, or both.
Is there a quick [1] way of creating a /dev/null style std::sync::mpsc
channel that simply discards everything the caller sends? The test code should
be clean and easy to review so I would like to avoid keeping the rx handles
around. There’s quite a few test cases so I’d prefer not to leak the handles either.
As long as you use channel() (and not sync_channel()), and the messages are not too many (which will cause a significant memory leak), you can just not recv() messages from the channel. When the receiver and the senders will drop, the memory will be freed.
When the receiver and the senders will drop, the memory
will be freed.
I’m currently doing that with sync_channel with a vastly
oversized buffer. It sorta works for testing purposes but having
to keep the rx handle around is what I’d prefer to avoid for
clarity’s sake.
I was going to suggest creating a trait, basically so you can have
an implementation for the real type
a mock implementation for testing
(I don't think this is always the best approach, because you can rapidly end up with many more traits than types if it's your go-to approach, but it has its place.)
Then you can also do impl MyTrait for RealThing, pass through the method calls you care about, and just pass the real thing in your non-test code without needing wrappers/conversions/etc.
I was also going to suggest a mock implementation, but instead of generics (which means more complicated code and longer compile times), I'd suggest conditional compilation. In the root of the crate:
#[cfg(not(test))]
pub(crate) use std::sync::mpsc as channel;
#[cfg(test)]
pub(crate) mod channel;
The instead of importing std::sync::mpsc, you import crate::channel.
I was also going to suggest a mock implementation, but instead
of generics (which means more complicated code and longer compile
times), I'd suggest conditional compilation. In the root of the
crate:
#[cfg(not(test))]
pub(crate) use std::sync::mpsc as channel;
#[cfg(test)]
pub(crate) mod channel;
I like this approach but the issue is that this would apply to
all unit tests whereas I would require the mock version only
for unit tests in a certain file. Elsewhere the channels should
be proper channels also during testing.
The problem is that you spawn a new thread for each test, which can be expensive. You can create one thread for the complete test suite, but the need for synchronization may make it even slower.
Thousands of short tests should be ok. This blog post I found on quick googling says it takes about tens of microseconds. The post uses C++ but it shouldn't be different in scale.
The Rust test harness already spawns a new thread for each unit test by default (unless running on a single-core processor or with RUST_TEST_THREADS=1), so you are already paying for this overhead once. If thread spawning is a significant part of your total test time, you might want to combine some of your tests into larger (and fewer) units.