I have an interface IAudioProcessor
with a single method IEnumerable<Sample> Process(IEnumerable<Sample> samples)
. While it is not a requirement of the interface itself, I want to make sure that all my implementations follow some common rules, like for example:
It is not hard to create tests for these, but I would have to copy and paste these tests for each implementation. I would like to avoid that.
I would like to do something like this (note the attribute GenericTest
and the type parameter):
[GenericTest(typeof(AudioProcessorImpl1Factory))]
[GenericTest(typeof(AudioProcessorImpl2Factory))]
[GenericTest(typeof(AudioProcessorImpl3Factory))]
public class when_processed_audio_is_returned<TSutFactory>
where TSutFactory : ISutFactory<IAudioProcessor>, new()
{
static IAudioProcessor Sut = new TSutFactory().CreateSut();
protected static Context _ = new Context();
Establish context = () => _.Original = Substitute.For<IEnumerable<ISample>>();
Because of = () => Sut.Process(_.Original);
It should_not_have_enumerated_the_original_samples = () =>
{
_.Original.DidNotReceive().GetEnumerator();
((IEnumerable)_.Original).DidNotReceive().GetEnumerator();
};
}
Is something like this possible?
I'm pretty sure you're looking for Behaviors (also see this row test with behaviors article). You will define the behaviors that every implementation should satisfy (the It
fields) in a special class that share the SUT and supporting fields (as necessary).
[Behaviors]
public class DeferredExecutionProcessor
{
It should_not_have_enumerated_the_original_samples = () =>
{
_.Original.DidNotReceive().GetEnumerator();
((IEnumerable)_.Original).DidNotReceive().GetEnumerator();
};
protected static Context _;
}
Each of your implementations need to declare that they behave like this special class. You already had a pretty complicated base class with shared setup and behavior, so I'll use it (I prefer a simpler, more explicit setup).
public abstract class AudioProcessorContext<TSutFactory>
where TSutFactory : ISutFactory<IAudioProcessor>, new()
{
// I don't know Behaves_like works with field initializers
Establish context = () =>
{
Sut = new TSutFactory().CreateSut();
_ = new Context();
_.Original = Substitute.For<IEnumerable<ISample>>();
}
protected static IAudioProcessor Sut;
protected static Context _;
}
Your base class defines the common setup (capturing the context enumeration), behavior (processing with the specific impl via the type parameter), and even declaring the behaviors field (again, thanks to the generic type parameter, this will be run for every concrete).
[Subject("Audio Processor Impl 1")]
public class when_impl1_processes_audio : AudioProcessorContext<AudioProcessorImpl1Factory>
{
Because of = () => Sut.Process(_.Original);
Behaves_like<DeferredExecutionProcessor> specs;
}
[Subject("Audio Processor Impl 2")]
public class when_impl2_processes_audio : AudioProcessorContext<AudioProcessorImpl2Factory>
{
Because of = () => Sut.Process(_.Original);
Behaves_like<DeferredExecutionProcessor> specs;
}
[Subject("Audio Processor Impl 3")]
public class when_impl3_processes_audio : AudioProcessorContext<AudioProcessorImpl3Factory>
{
Because of = () => Sut.Process(_.Original);
Behaves_like<DeferredExecutionProcessor> specs;
}
Additionally, you will get output for each of the It
fields for each of the implementing classes. So your context/spec reports will be complete.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With