【问题标题】:Setting property value on child instance to a fixed value with Autofixture使用 Autofixture 将子实例上的属性值设置为固定值
【发布时间】:2015-03-05 02:26:17
【问题描述】:

是否可以在使用 Autofixture 构建父级时为子实例上的属性分配固定值?它会像魅力一样为子实例上的所有属性添加默认值,但我想覆盖并为子实例上的某个属性分配一个特定值。

鉴于这种父/子关系:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public int Number { get; set; }
    public string City { get; set; }
    public string PostalCode { get; set; }
}

我想为地址实例上的 City 属性分配一个特定值。我在想这个测试代码的行:

var fixture = new Fixture();

var expectedCity = "foo";

var person = fixture
    .Build<Person>()
    .With(x => x.Address.City, expectedCity)
    .Create();

Assert.AreEqual(expectedCity, person.Address.City);

这是不可能的。我猜是反射异常

System.Reflection.TargetException : Object does not match target type.

...Autofixture 尝试将值分配给 Person 实例上的 City 属性,而不是 Address 实例。

有什么建议吗?

是的,我知道我可以添加一个额外的步骤,如下所示:

var fixture = new Fixture();

var expectedCity = "foo";

// extra step begin
var address = fixture
    .Build<Address>()
    .With(x => x.City, expectedCity)
    .Create();
// extra step end

var person = fixture
    .Build<Person>()
    .With(x => x.Address, address)
    .Create();

Assert.AreEqual(expectedCity, person.Address.City);

...但希望有第一个版本或类似的东西(步骤更少,更简洁)。

注意:我使用的是 Autofixture v3.22.0

【问题讨论】:

标签: c# hierarchy autofixture


【解决方案1】:

为了完整起见,这里有另一种方法:

fixture.Customize<Address>(c => 
    c.With(addr => addr.City, "foo"));

var person = fixture.Create<Person>();

这将自定义Address的所有实例的创建

如果您最终经常使用它,可能值得将其包装在 ICustomization 中:

public class AddressConventions : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Address>(c => 
            c.With(addr => addr.City, "foo"));
    }
}

fixture.Customize(new AddressConventions());

【讨论】:

    【解决方案2】:

    不要对这个问题不屑一顾,但最简单的解决方案实际上可能是这样的:

    [Fact]
    public void SimplestThingThatCouldPossiblyWork()
    {
        var fixture = new Fixture();
        var expectedCity = "foo";
        var person = fixture.Create<Person>();
        person.Address.City = expectedCity;
        Assert.Equal(expectedCity, person.Address.City);
    }
    

    将显式值分配给属性是大多数语言已经擅长的事情(C# 确实如此),所以我认为 AutoFixture 不需要复杂的 DSL 来重现该功能的一半。

    【讨论】:

    • 虽然对我们大多数人来说很明显,在 C# 中为属性分配显式值(几乎)是毫不费力的,但最初的问题是我可以在父/子关系中为子 with Autofixture 的属性。问题的原因仅仅是美学,因为我认为使用 .With(....) 语法对值分配进行分组在有多个分配时会“更漂亮”(尽管主​​观上如此)。抛开美学不谈,您的回答可以完成工作。
    • 您可以通过实现自定义ISpecimenBuilder 来修复一个值,但当它是 Train Wreck 属性时(我认为)不能使用流利的 DSL。我确实从你的目标是漂亮的问题中得到了印象,但如果你想要基于约定的方法,我很乐意分享它。
    • 不,不过还是谢谢。火车残骸停在我的应用程序的最外层边界,只是对传入的数据结构进行建模,所以我将处理一些看起来……嗯……不那么漂亮的测试用例;-)
    • 我遇到了这个问题,因为我有同样的问题。在我看来,AF 的一大优点是能够使用流畅的 API。令我惊讶的是,AF 不支持这一点。由于我能够流利地编写代码,它应该可以工作。对我来说,这是一个“最小惊讶”的案例。
    • 2020 年不再是公共二传手
    【解决方案3】:

    您可以在构建器上使用 Do 方法:

    var person = this.fixture
                   .Build<Person>()
                   .Do( x => x.Address.City = expectedCity )
                   .Create();
    

    您还可以将您的实例注入并冻结到夹具:

    this.fixture.Inject( expectedCity );
    

    【讨论】:

    • 您是否真的尝试过运行该代码?当我运行它时它会抛出一个 NullReferenceException。
    • ...Inject 会将所有字符串属性设置为 expectedCity 的值——这不是我真正想要的
    • 确实,Do方法有一个NullReferenceException,IMO可以认为是bug,因为这个方法来自名为IPostprocessComposer i>,所以后处理应该在创建对象及其依赖项之后进行。
    • 最简单的解决方案就是对象创建后的assign语句:person.Address.City = expectedCity;
    • 看起来你是对的,这是一个错误。根据其设计者博客 (blog.ploeh.dk/2009/06/09/…),“您可以将 With 和 Do 混合使用,ObjectBuilder 将保留顺序。”
    猜你喜欢
    • 1970-01-01
    • 2020-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-21
    • 1970-01-01
    • 2018-11-13
    • 1970-01-01
    相关资源
    最近更新 更多