对于 Autofixture 中的所有实现,省略基础 class 的 属性

Omit property of base class for all implementations in Autofixture

我有 class 的例子如下:

public class BaseItem
{
    public Guid Id { get; set; }

    public Language Language { get; set; }
}

public class ItemA : BaseItem
{
    public string Name { get; set; }
}

public class ItemB : BaseItem
{
    public string Path { get; set; }
}

我想在每次创建 ItemAItemBBaseItem class 的任何未来实现时省略 属性 Language

我在自定义 class 中添加了这一行(下方),但在生成 ItemA/ ItemB 时,AutoFixture 仍会尝试生成 Language 属性.

fixture.Register<BaseItem>(() => { return fixture.Build<BaseItem>().Without(i => i.Language).Create(); });

像这样(见下文)在实现 class 级别上省略 属性 是可行的。

fixture.Register<ItemA>(() => { return fixture.Build<ItemA>().Without(i => i.Language).Create(); });

我的问题是:

  1. 我如何实现一种通用的方式来做到这一点,这样我就不必自定义每个实现 class?
  2. 如果有办法执行 #1,如果我希望为某些异常生成语言 属性,我该如何覆盖它?

Q1

为了定位(或者更确切地说,省略)在基础class上定义的属性,我知道没有比定义更好的方法了自定义 ISpecimenBuilder,在本例中类似于:

public class OmitLanguageBuilder : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        var pi = request as PropertyInfo;
        if (pi != null &&
            pi.PropertyType == typeof(Language)
            && pi.Name == "Language"
            && pi.DeclaringType == typeof(BaseItem))
            return new OmitSpecimen();

        return new NoSpecimen();
    }
}

这专门处理仅在 BaseItem 上声明的 Language 属性 的请求。如果你想不那么具体,你可以删除 if 表达式中的一些谓词。当 AutoFixture 收到这样的请求时,它会将特殊的 'signal type' OmitSpecimen 解释为忽略 属性.

的指令

你可以直接用Fixture实例注册OmitLanguageBuilder(虽然你应该将它打包在自定义中。这个测试通过:

[Fact]
public void Q1A()
{
    var fixture = new Fixture();
    fixture.Customizations.Add(new OmitLanguageBuilder());

    var actual = fixture.Create<ItemA>();

    Assert.Null(actual.Language);
    Assert.NotEqual(default(Guid), actual.Id);
    Assert.NotNull(actual.Name);
}

ItemB 的类似测试也通过了。

Q2

当您想在特殊测试用例中填充 Language 时,最简单的解决方案可能只是在事后填充它:

[Fact]
public void Q2SetAfter()
{
    var fixture = new Fixture();
    fixture.Customizations.Add(new OmitLanguageBuilder());

    var actual = fixture.Create<ItemA>();
    actual.Language = fixture.Create<Language>();

    Assert.NotNull(actual.Language);
    Assert.NotEqual(default(Guid), actual.Id);
    Assert.NotNull(actual.Name);
}

另一种选择是使用 Build API:

[Fact]
public void Q2Build()
{
    var fixture = new Fixture();
    fixture.Customizations.Add(new OmitLanguageBuilder());

    var actual = 
        fixture.Build<ItemA>().With(x => x.Language, fixture.Create<Language>()).Create();

    Assert.NotNull(actual.Language);
    Assert.NotEqual(default(Guid), actual.Id);
    Assert.NotNull(actual.Name);
}

最后,如果您需要更改 AutoFixture 处理类型的一般方式,您可以这样做:

[Fact]
public void Q2Customize()
{
    var fixture = new Fixture();
    fixture.Customizations.Add(new OmitLanguageBuilder());
    fixture.Customize<ItemA>(c => c.With(x => x.Language, fixture.Create<Language>()));

    var actual = fixture.Create<ItemA>();

    Assert.NotNull(actual.Language);
    Assert.NotEqual(default(Guid), actual.Id);
    Assert.NotNull(actual.Name);
}