While trying to unit test a service method, the base abstract class constructor that it implements contains 2 parameters. I want to customise these parameters while invoking the service using Auto-fixture.
The base service code is as below.
public abstract class ServiceBase : IHostedService, IDisposable
{
private readonly CronExpression _expression;
private readonly TimeZoneInfo _timeZoneInfo;
protected BaseService(string cronExpression, TimeZoneInfo timeZoneInfo)
{
_expression = CronExpression.Parse(cronExpression);
_timeZoneInfo = timeZoneInfo;
}
public abstract Task ExecuteTask(CancellationToken cancellationToken);
.
.
}
Another Service inherits from this base abstract class
public class TestService : ServiceBase
{
public override async Task ExecuteTask(CancellationToken stoppingToken)
{
//Implementation here
}
}
In my Unit test I'm invoking the ExecuteTask
function as below
Func<Task> executeAction = async () => await sut.ExecuteTask(A<CancellationToken>._);
executeAction.Should().NotThrow();
AutoFixture
tries to pass a random string to the CronExpression
constructor parameter in BaseService
class. Problem is this CronExpression
has to be in a specific format or else an error occurs while trying to Parse it CronExpression.Parse(cronExpression)
How can i pass a custom value for constructor parameters?
Thanks in advance.
AutoFixture doesn't know about the domain constraints for the string
parameter to your class. This should give a hint that, perhaps this type isn't the most appropriate parameter to the class. This is a classic example of Primitive Obsession.
You might enhance your service to, instead, directly accept the type that encapsulates the idea that this is a CronExpression:
protected ServiceBase(CronExpression cronExpression, TimeZoneInfo timeZoneInfo)
{
_cronExpression = cronExpression;
...
}
This way, it's guaranteed that the BaseService
will be passed a valid CronExpression
. If it wasn't, then creation of the CronExpression
will have already failed.
You might think that we've simply shifted the problem: How do you now tell AutoFixture to create a valid CronExpression
?
The advantage of shifting the work to building a CronExpression
is that any customization made to create a valid CronExpression
is now reusable for any other type that will require it. This will reduce ceremony for any future tests that will end up needing that type. Not to mention all the other benefits of avoiding primitive obsession.
Regarding telling AutoFixture how to create a valid CronExpression
, there are many options. You might directly inject a single valid one:
fixture.Inject(CronExpression.Parse("myValidCronExpression"));
If you want to be able to select multiple, you could use ElementsBuilder
to select from a pool of valid values:
public void Test()
{
var fixture = new Fixture();
fixture.Customizations.Add(new ElementsBuilder<CronExpression>(
new[] { "validExpr1", "validExpr2" }
.Select(s => CronExpression.Parse(s))
.ToArray()))
...
}
The most control of creation for that type would be afforded by creating an implementation of ISpecimenBuilder
, which I'll omit in this answer. If you want to go that route, there are many examples both here and elsewhere.
After one of those customizations is made to AutoFixture, creation of your sut will work:
var sut = fixture.Create<TestService>();
Note: I haven't tested if TimeZoneInfo
can directly be instantiated by AutoFixture's default specimen builders. If it can't, a similar method can be used for that type as well.