Say I have a Service
that has a dependency on TimeProvider
.
What I do now to setup the use of FakeTimeProvider
for my tests using AutoFixture and Moq is:
public class FakeTimeProviderCustomization : ICustomization {
public void Customize(IFixture fixture) {
fixture.Customize<TimeProvider>
(c => c.FromFactory(() => new FakeTimeProvider()));
}
}
public class MoqAutoDataAttribute : AutoDataAttribute {
public MoqAutoDataAttribute()
: base(() => new Fixture()
.Customize(new AutoMoqCustomization())
.Customize(new FakeTimeProviderCustomization())
) {
}
}
[Theory, MoqAutoData]
public async Task Test_TimeRelatedStuff(
[Frozen] TimeProvider timeProvider,
Service sut) {
...
var fakeTimeProvider = timeProvider as FakeTimeProvider;
...
Can I avoid the cast to FakeTimeProvider
that I currently have to make each time?
You can use the Matching
API provided by the [Frozen]
attribute, like shown below, but that requires that you deactivate FakeTimeProviderCustomization
.
This test passes:
[Theory, MoqAutoData]
public async Task Test_TimeRelatedStuff(
[Frozen(Matching.DirectBaseType)] FakeTimeProvider fakeTimeProvider,
Service sut)
{
//... Cast is no longer necessary
//var fakeTimeProvider = timeProvider as FakeTimeProvider;
//...
Assert.Equal(fakeTimeProvider, sut.TimeProvider);
}
where I've defined Service
like this for demonstration purposes:
public sealed class Service(TimeProvider timeProvider)
{
public TimeProvider TimeProvider { get; } = timeProvider;
}
As already underlined, the Matching.DirectBaseType
criterion doesn't play well with the Customize
API, in the sense that the latter overrides the former. To make the above test pass, I deactivated the FakeTimeProviderCustomization
:
public class FakeTimeProviderCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
//fixture.Customize<TimeProvider>
// (c => c.FromFactory(() => new FakeTimeProvider()));
}
}
Obviously, this renders the class ineffective, so you might as well delete it. I'm only showing the above here in order to explicitly explain how to make the test pass.
Alternatively, if you don't want to deactivate FakeTimeProviderCustomization
, you can use another matching rule. If your Service
constructor looks like Service(TimeProvider timeProvider)
, you can alternatively write the test like this:
[Theory, MoqAutoData]
public async Task Test_TimeRelatedStuff(
[Frozen(Matching.ParameterName)] FakeTimeProvider timeProvider,
Service sut)
{
//... Cast is no longer necessary
//var fakeTimeProvider = timeProvider as FakeTimeProvider;
//...
Assert.Equal(timeProvider, sut.TimeProvider);
}
Notice that the test parameter name timeProvider
is the same as the dependency name for Service
. This works even if you keep FakeTimeProviderCustomization
active for other reasons.
Obviously, as these provisos suggest, both of these 'solutions' are brittle, so you best course of action might be to reconsider the overall API design and test strategy.