【问题标题】:Can AutoFixture execute a delegate at object creation time?AutoFixture 可以在对象创建时执行委托吗?
【发布时间】:2012-04-19 10:12:16
【问题描述】:

我希望自定义 AutoFixture 的创建时行为,以便在生成和分配固定装置的属性后设置一些依赖对象。

例如,假设我有一个自定义 User 的方法,因为它的 IsDeleted 属性对于某些测试集总是必须为 false:

public class User
{
   public int Id { get; set; }
   public string Name { get; set; }
   public bool IsDeleted { get; set; }
}

public static ObjectBuilder<User> BuildUser(this Fixture f)
{
   return f.Build<User>().With(u => u.IsDeleted, false);
}

(我将ObjectBuilder 交给测试,以便在必要时进一步自定义夹具。)

我想做的是在创建时通过其Id 自动将该用户与匿名集合相关联,但我不能按原样执行此操作,因为在我之前还没有生成Id将返回值正确地交给单元测试。这是我正在尝试做的事情:

public static ObjectBuilder<User> BuildUserIn(this Fixture f, UserCollection uc)
{
   return f.Build<User>()
           .With(u => u.IsDeleted, false);
           .AfterCreation(u =>
            {
               var relation = f.Build<UserCollectionMembership>()
                               .With(ucm => ucm.UserCollectionId, uc.Id)
                               .With(ucm => ucm.UserId, u.Id)
                               .CreateAnonymous();
               Repository.Install(relation);
            }
}

这样的事情可能吗?或者也许有更好的方法来实现我创建匿名对象图的目标?

【问题讨论】:

  • 您想为 User 类型创建一个特定实例并在其他地方重用它的 Id 属性值吗?
  • @MarkSeemann: Do() 显然可以在对象完全填充之前执行,所以这是行不通的。我可以(并且确实)手动执行示例 lambda 或在创建后进行对象自定义,但我喜欢类似于上述的替代方法!

标签: c# autofixture


【解决方案1】:

对于Build 方法,这是不可能的,而且可能永远不会,因为有更好的选择。

首先,绝对没有必要围绕Build 方法编写静态辅助方法。 Build 方法用于真正的一次性初始化,其中需要在事前定义属性或字段值。

即想象这样一个类:

public class MyClass
{
    private string txt;

    public string SomeWeirdText
    {
        get { return this.txt; }
        set
        {
            if (value != "bar")
                throw new ArgumentException();
            this.txt = value;
        }
    }
}

在这个(人为的)示例中,直接fixture.CreateAnonymous&lt;MyClass&gt; 将抛出,因为它会尝试将“bar”以外的其他内容分配给属性。

在一次性场景中,可以使用Build 方法来避免此问题。一个例子是简单地将值显式设置为“bar”:

var mc =
    fixture.Build<MyClass>().With(x => x.SomeWeirdText, "bar").CreateAnonymous();

但是,省略该属性会更简单:

var mc =
    fixture.Build<MyClass>().Without(x => x.SomeWeirdText).CreateAnonymous();

但是,一旦您开始想要反复执行此操作,就有更好的选择。 AutoFixture 有一个非常复杂且可定制的引擎,用于定义事物的创建方式。

首先,可以先将属性的省略移到自定义中,如下所示:

fixture.Customize<MyClass>(c => c.Without(x => x.SomeWeirdText));

现在,每当夹具创建 MyClass 的实例时,它都会完全跳过该属性。之后您仍然可以分配一个值:

var mc = fixture.CreateAnonymous<MyClass>();
my.SomeWeirdText = "bar";

如果你想要更复杂的东西,你可以implement a custom ISpecimenBuilder。如果您想在创建实例后运行一些自定义代码,您可以使用后处理器装饰您自己的 ISpecimenBuilder 并提供一个委托。这可能看起来像这样:

fixture.Customizations.Add(
    new Postprocessor(yourCustomSpecimenBuilder, obj =>
        { */ do something to obj here */ }));

(顺便说一句,你还在使用 AutoFixture 1.0 吗?IIRC,从那时起就没有 ObjectBuilder&lt;T&gt;...)

【讨论】:

  • 添加后处理器可能只是个问题,我很欣赏有关正确使用 AF 的建议。谢谢! (关于 v1.0,是的:我们有一些 trouble upgrading - 也许第三次将是魅力!)
【解决方案2】:

有一个useful discussion on this topic on the AutoFixture CodePlex site

我相信我在那里链接的postprocessor Customization 应该对您有所帮助。示例用法:

class AutoControllerDataAttribute : AutoDataAttribute
{  
    public AutoControllerDataAttribute()
        : this( new Fixture() )
    {
    }

    public AutoControllerDataAttribute( IFixture fixture )
        : base( fixture )
    {
        fixture.Customize( new AutoMoqCustomization() );
        fixture.Customize( new ApplyControllerContextCustomization() );
    }

    class ApplyControllerContextCustomization : PostProcessWhereIsACustomization<Controller>
    {
        public ApplyControllerContextCustomization()
            : base( PostProcess )
        {
        }

        static void PostProcess( Controller controller )
        {
            controller.FakeControllerContext();
            // etc. - add stuff you want to happen after the instance has been created

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-16
    • 1970-01-01
    • 2011-07-05
    • 1970-01-01
    相关资源
    最近更新 更多