AutoFixture能否在对象创建时执行委托?

7
我希望能够自定义创建时间行为的 AutoFixture,以便在生成和分配 fixture 属性后设置一些依赖对象。
例如,假设我有一个方法,该方法定制了一个 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属性值吗? - Nikos Baxevanis
2
这个有帮助吗?https://dev59.com/zW035IYBdhLWcg3wef5y#5398653 - Mark Seemann
@MarkSeemann:Do() 显然可以在对象完全填充之前执行,所以这不起作用。我可以(并且确实)手动执行示例 lambda 或在创建后进行对象自定义,但我希望有一种类似于上面的替代方法! - ladenedge
2个回答

6
对于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<MyClass>的方法将会抛出异常,因为它将尝试将其他值赋给属性而非“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));

现在,每当fixture创建一个MyClass的实例时,它都将完全跳过该属性。您仍然可以之后分配一个值:
var mc = fixture.CreateAnonymous<MyClass>();
my.SomeWeirdText = "bar";

如果您想要更高级的东西,您可以实现自定义ISpecimenBuilder。如果您想在实例创建后运行一些自定义代码,您可以使用Postprocessor装饰自己的ISpecimenBuilder并提供一个委托。可能会像这样:
fixture.Customizations.Add(
    new Postprocessor(yourCustomSpecimenBuilder, obj =>
        { */ do something to obj here */ }));

(顺便问一句,你还在使用AutoFixture 1.0吗?如果我没记错的话,自那时起就没有ObjectBuilder<T>了...)

添加一个后处理器可能正是解决问题的方法,我很感谢有关正确使用 AF 的建议。谢谢!关于 v1.0,确实存在一些问题(参见 https://dev59.com/j1PTa4cB1Zd3GeqPhkX8 和 https://dev59.com/NF7Va4cB1Zd3GeqPKol0),也许第三次会更顺利! - ladenedge

3

在AutoFixture CodePlex网站上有一个关于这个主题的有用的讨论

我相信我链接到那里的后处理器自定义应该会对你有所帮助。使用示例:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接