【问题标题】:Customising constructor inputs to an abstract class in AutoFixture自定义构造函数输入到 AutoFixture 中的抽象类
【发布时间】:2020-04-29 07:59:37
【问题描述】:

在尝试对服务方法进行单元测试时,它实现的基本抽象类构造函数包含 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);
   .
   .
}

另一个服务继承自这个基础抽象类

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 类中的CronExpression 构造函数参数。问题是这个CronExpression 必须采用特定格式,否则在尝试解析它时会发生错误CronExpression.Parse(cronExpression)

如何为构造函数参数传递自定义值?

提前致谢。

【问题讨论】:

    标签: c# .net-core abstract-class mstest autofixture


    【解决方案1】:

    AutoFixture 不知道您的类的 string 参数的域约束。这应该暗示,也许这种类型不是类的最合适的参数。这是Primitive Obsession的经典例子。

    您可以增强您的服务,改为直接接受封装了这是 CronExpression 的想法的类型:

    protected ServiceBase(CronExpression cronExpression, TimeZoneInfo timeZoneInfo)
    {
        _cronExpression = cronExpression;
        ...
    }
    

    这样,可以保证BaseService 将被传递一个有效的CronExpression。如果不是,那么 CronExpression 的创建将已经失败。

    您可能认为我们只是转移了问题:您现在如何告诉 AutoFixture 创建一个有效的CronExpression

    将工作转移到构建CronExpression 的优势在于,为创建有效CronExpression 而进行的任何自定义现在可重用 用于需要它的任何其他类型。这将减少最终需要该类型的任何未来测试的仪式。更不用说避免原始痴迷的所有其他好处。

    关于告诉 AutoFixture 如何创建一个有效的CronExpression,有很多选择。您可以直接注入一个有效的:

    fixture.Inject(CronExpression.Parse("myValidCronExpression"));
    
    

    如果您希望能够选择多个,可以使用ElementsBuilder 从有效值池中进行选择:

    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 的默认样本生成器实例化。如果不能,也可以对该类型使用类似的方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-02
      • 2011-08-01
      • 1970-01-01
      • 2023-03-04
      • 2015-03-20
      相关资源
      最近更新 更多