如何在保持类型自定义的同时使用AutoFixture构建自定义属性?

时间:2016-07-31 22:34:46

标签: c# .net unit-testing autofixture

我正在尝试使用autofixture来创建一个对象,但它们是我想要始终默认的某些属性(其余的可以自动生成)。但是,每当我设置自定义时,它都会在我使用自定义构建时被覆盖。

void Main()
{
    var fixture = new Fixture();
    fixture.Customize<Person>(composer => composer.With(p => p.Name, "Ben"));

    var person = fixture.Build<Person>()
        .With(p => p.DateOfBirth, new DateTime(1900, 1, 1))
        .Create();

    /*  RESULT OF person below
    Name    null
    DateOfBirth 1/1/1900
    StreetAddress   StreetAddressafd6b86b-376a-4355-9a9c-fbae34731453
    State   State019e867b-ac5e-418f-805b-a64146bc06bc
    */
}

public class Person
{
    public string Name { get; set;}

    public DateTime DateOfBirth { get; set;}

    public string StreetAddress { get; set;}

    public string State { get; set;}
}

“Name”和“DateOfBirth”属性自定义不冲突,所以我不知道为什么Name最终为null。我希望名字是“Ben”。

我怎样才能获得它,以便应用两种自定义(即Name =“Ben”和DateOfBirth = 1/1/1900?

3 个答案:

答案 0 :(得分:12)

正如@DavidOsborne正确指出的那样,您所看到的行为是as designed

better approach是将您的自定义组织在单独的类中,然后根据特定测试方案的需要启用它们。

自定义对象实现ICustomization接口,其作用是以特定方式配置Fixture对象。这是一个例子:

public class AllPersonsAreNamedBen : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Person>(composer =>
            composer.With(p => p.Name, "Ben"));
    }
}

public class AllPersonsAreBornIn1900 : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Person>(composer =>
            composer.With(p => p.DateOfBirth, new DateTime(1900, 1, 1)));
    }
}

您可以使用Fixture方法在特定Customize上启用自定义,例如:

fixture.Customize(new AllPersonsAreNamedBen());

或:

fixture.Customize(new AllPersonsAreBornIn1900());

您还可以使用CompositeCustomization类将多个自定义组合成一个新自定义:

public class AllPersonsAreNamedBenAndAreBornIn1900 : CompositeCustomization
{
    public AllPersonsAreNamedBenAndAreBornIn1900()
        : base(new AllPersonsAreNamedBen(),
               new AllPersonsAreBornIn1900())
    {
    }
}

此时您可以简单地说:

fixture.Customize(new AllPersonsAreNamedBenAndAreBornIn1900());

但是,请记住,自定义应用于Fixture的顺序很重要:最后一个获胜并且可能覆盖之前的那些,正如@MarkSeemann指出的那样在评论中。这也是by design

因此,虽然可以组合使用不同类型的现有自定义项,但在这种特殊情况下,由于两个自定义项都定位于同一类型,因此您将拥有创建新的自定义以封装Person类型组合的所有设置:

public class AllPersonsAreNamedBenAndAreBornIn1900 : CompositeCustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Person>(composer =>
            composer.With(p => p.Name, "Ben")
                    .With(p => p.DateOfBirth, new DateTime(1900, 1, 1)));
    }
}

作为一般规则,保持自定义较小且集中使您能够在不同的测试中reuse them,将它们组合到特定的测试场景中。

答案 1 :(得分:9)

这看起来像是设计:

  

请注意,Build方法链最好被理解为一次性Customization。它绕过Fixture实例上的所有自定义项。相反,它可以在构建特定样本时进行细粒度控制。但是,在大多数情况下,添加基于约定的ICustomization是更好,更灵活的选项。

...来自Build()方法的文档。

我很欣赏这可能不是一个理想的答案。但是,文档确实提供了如何到达一个文档的提示。

答案 2 :(得分:0)

如已接受的答案所述,自定义会相互覆盖,因此您必须为要使用的每种属性组合编写一个自定义。

我写了一个库,允许您编写自定义项,这样它们就不会彼此覆盖,而是会合并在一起。这样一来,您可以对People named Ben进行一个自定义,而对Born in 1900进行一个自定义,然后可以将它们与Compose函数一起进行组合。

它并没有完全解决最初的问题,但是可能会给您一些有关如何实现所需目标的想法。通过创建可处理自定义内容的自定义IFixture实现来完成Compose功能。

https://github.com/LethargicDeveloper/Lift.AutoFixture

编辑:我看到“ 4个月前处于活动状态”。我错过了“ 3年”。很抱歉复活了这么旧的帖子,但希望这会有用。