在 AutoFixture 中将构造函数输入自定义为抽象 class
Customising constructor inputs to an abstract class in AutoFixture
在尝试对服务方法进行单元测试时,它实现的基本抽象 class 构造函数包含 2 个参数。我想在使用 Auto-fixture 调用服务时自定义这些参数。
基础服务代码如下
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);
.
.
}
另一个服务继承自这个基础抽象class
public class TestService : ServiceBase
{
public override async Task ExecuteTask(CancellationToken stoppingToken)
{
//Implementation here
}
}
在我的单元测试中,我正在调用 ExecuteTask
函数,如下所示
Func<Task> executeAction = async () => await sut.ExecuteTask(A<CancellationToken>._);
executeAction.Should().NotThrow();
AutoFixture
尝试将随机字符串传递给 BaseService
class 中的 CronExpression
构造函数参数。问题是这个 CronExpression
必须采用特定格式,否则在尝试解析它时会发生错误 CronExpression.Parse(cronExpression)
如何为构造函数参数传递自定义值?
提前致谢。
AutoFixture 不知道 class 的 string
参数的域约束。这应该暗示,也许这种类型不是 class 最合适的参数。这是 Primitive Obsession 的 classic 示例。
您可能会增强您的服务,而不是直接接受封装了这是 CronExpression 的想法的类型:
protected ServiceBase(CronExpression cronExpression, TimeZoneInfo timeZoneInfo)
{
_cronExpression = cronExpression;
...
}
这样,可以保证 BaseService
将传递一个有效的 CronExpression
。如果不是,那么 CronExpression
的创建将已经失败。
您可能认为我们只是转移了问题:您现在如何告诉 AutoFixture 创建有效的 CronExpression
?
将工作转移到构建 CronExpression
的优势在于,为创建有效 CronExpression
所做的任何定制现在都可以 重用 用于任何其他类型那将需要它。这将减少任何最终需要该类型的未来测试的仪式。更不用说避免原始痴迷的所有其他好处了。
关于告诉 AutoFixture 如何创建一个有效的CronExpression
,有很多选项。您可以直接注入一个有效的:
fixture.Inject(CronExpression.Parse("myValidCronExpression"));
如果您希望能够 select 多个,您可以使用 ElementsBuilder
到 select 来自有效值的池:
public void Test()
{
var fixture = new Fixture();
fixture.Customizations.Add(new ElementsBuilder<CronExpression>(
new[] { "validExpr1", "validExpr2" }
.Select(s => CronExpression.Parse(s))
.ToArray()))
...
}
通过创建 ISpecimenBuilder
的实现,可以最大程度地控制该类型的创建,我将在本答案中省略它。如果您想走那条路,这里和其他地方都有很多例子。
在对 AutoFixture 进行其中一项自定义后,您的 sut 的创建将起作用:
var sut = fixture.Create<TestService>();
注意:我还没有测试 TimeZoneInfo
是否可以直接由 AutoFixture 的默认样本生成器实例化。如果不能,也可以对该类型使用类似的方法。
在尝试对服务方法进行单元测试时,它实现的基本抽象 class 构造函数包含 2 个参数。我想在使用 Auto-fixture 调用服务时自定义这些参数。
基础服务代码如下
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);
.
.
}
另一个服务继承自这个基础抽象class
public class TestService : ServiceBase
{
public override async Task ExecuteTask(CancellationToken stoppingToken)
{
//Implementation here
}
}
在我的单元测试中,我正在调用 ExecuteTask
函数,如下所示
Func<Task> executeAction = async () => await sut.ExecuteTask(A<CancellationToken>._);
executeAction.Should().NotThrow();
AutoFixture
尝试将随机字符串传递给 BaseService
class 中的 CronExpression
构造函数参数。问题是这个 CronExpression
必须采用特定格式,否则在尝试解析它时会发生错误 CronExpression.Parse(cronExpression)
如何为构造函数参数传递自定义值?
提前致谢。
AutoFixture 不知道 class 的 string
参数的域约束。这应该暗示,也许这种类型不是 class 最合适的参数。这是 Primitive Obsession 的 classic 示例。
您可能会增强您的服务,而不是直接接受封装了这是 CronExpression 的想法的类型:
protected ServiceBase(CronExpression cronExpression, TimeZoneInfo timeZoneInfo)
{
_cronExpression = cronExpression;
...
}
这样,可以保证 BaseService
将传递一个有效的 CronExpression
。如果不是,那么 CronExpression
的创建将已经失败。
您可能认为我们只是转移了问题:您现在如何告诉 AutoFixture 创建有效的 CronExpression
?
将工作转移到构建 CronExpression
的优势在于,为创建有效 CronExpression
所做的任何定制现在都可以 重用 用于任何其他类型那将需要它。这将减少任何最终需要该类型的未来测试的仪式。更不用说避免原始痴迷的所有其他好处了。
关于告诉 AutoFixture 如何创建一个有效的CronExpression
,有很多选项。您可以直接注入一个有效的:
fixture.Inject(CronExpression.Parse("myValidCronExpression"));
如果您希望能够 select 多个,您可以使用 ElementsBuilder
到 select 来自有效值的池:
public void Test()
{
var fixture = new Fixture();
fixture.Customizations.Add(new ElementsBuilder<CronExpression>(
new[] { "validExpr1", "validExpr2" }
.Select(s => CronExpression.Parse(s))
.ToArray()))
...
}
通过创建 ISpecimenBuilder
的实现,可以最大程度地控制该类型的创建,我将在本答案中省略它。如果您想走那条路,这里和其他地方都有很多例子。
在对 AutoFixture 进行其中一项自定义后,您的 sut 的创建将起作用:
var sut = fixture.Create<TestService>();
注意:我还没有测试 TimeZoneInfo
是否可以直接由 AutoFixture 的默认样本生成器实例化。如果不能,也可以对该类型使用类似的方法。