【问题标题】:How can I use custom ISpecimenBuilders with OmitOnRecursionBehavior?如何将自定义 ISpecimenBuilders 与 OmitOnRecursionBehavior 一起使用?
【发布时间】:2013-09-03 18:03:43
【问题描述】:

如何使用自定义 ISpecimenBuilder 实例以及我希望全局应用于所有夹具创建的对象的 OmitOnRecursionBehavior

我正在使用带有恶臭循环引用的 EF Code First 模型,就本问题而言,它无法消除:

public class Parent {
    public string Name { get; set; }
    public int Age { get; set; }
    public virtual Child Child { get; set; }
}

public class Child {
    public string Name { get; set; }
    public int Age { get; set; }
    public virtual Parent Parent { get; set; }
}

我熟悉绕过循环引用的技术,就像在这个通过测试中一样:

[Theory, AutoData]
public void CanCreatePatientGraphWithAutoFixtureManually(Fixture fixture)
{
    //fixture.Customizations.Add(new ParentSpecimenBuilder());
    //fixture.Customizations.Add(new ChildSpecimenBuilder());
    fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
                     .ForEach(b => fixture.Behaviors.Remove(b));
    fixture.Behaviors.Add(new OmitOnRecursionBehavior());
    fixture.Behaviors.Add(new TracingBehavior());
    var parent = fixture.Create<Parent>();
    parent.Should().NotBeNull();
    parent.Child.Should().NotBeNull();
    parent.Child.Parent.Should().BeNull();
}

但如果其中一个/两个自定义项都未注释,我会收到异常:

System.InvalidCastException: Unable to cast object of type
'Ploeh.AutoFixture.Kernel.OmitSpecimen' to type 'CircularReference.Parent'.

当我调用ISpecimenContext 来解析Parent 并且请求来自正在解析的Child 时,我的ISpecimenBuilder 实现(显示在此问题的底部)中发生了失败的转换。我可以防范来自Child 解析操作的请求,如下所示:

//...
&& propertyInfo.ReflectedType != typeof(Child)
//...

但是,这似乎污染了ISpecimenBuilder 实现,因为知道“谁”可能会提出请求。此外,它似乎重复了“全球”OmitOnRecursionBehavior 的工作。

我想使用ISpecimenBuilder 实例,因为除了处理循环引用之外,我还有其他事情要自定义。我花了很多时间在 SO 和 Ploeh 上寻找此类场景的示例,但我还没有找到任何讨论行为和自定义组合的内容。重要的是解决方案是我可以用ICustomization 封装的解决方案,而不是

的测试设置中的行和行
//...
fixture.ActLikeThis(new SpecialBehavior())
       .WhenGiven(typeof (Parent))
       .AndDoNotEvenThinkAboutBuilding(typeof(Child))
       .UnlessParentIsNull()
//...

...因为最终我想为测试扩展一个 [AutoData] 属性。

以下是我的 ISpecimenBuilder 实现和失败测试的 TracingBehavior 输出:

public class ChildSpecimenBuilder : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        var propertyInfo = request as PropertyInfo;
        return propertyInfo != null
               && propertyInfo.PropertyType == typeof(Child)
                   ? Resolve(context)
                   : new NoSpecimen(request);
    }

    private static object Resolve(ISpecimenContext context)
    {
        var child = (Child) context.Resolve(typeof (Child));
        child.Name = context.Resolve(typeof (string)).ToString().ToLowerInvariant();
        child.Age = Math.Min(17, (int) context.Resolve(typeof (int)));
        return child;
    }
}

public class ParentSpecimenBuilder : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        var propertyInfo = request as PropertyInfo;
        return propertyInfo != null
               && propertyInfo.PropertyType == typeof (Parent)
                   ? Resolve(context)
                   : new NoSpecimen(request);
    }

    private static object Resolve(ISpecimenContext context)
    {
        var parent = (Parent) context.Resolve(typeof (Parent));
        parent.Name = context.Resolve(typeof (string)).ToString().ToUpperInvariant();
        parent.Age = Math.Max(18, (int) context.Resolve(typeof (int)));
        return parent;
    }
}

CanCreatePatientGraphWithAutoFixtureManually(fixture: Ploeh.AutoFixture.Fixture) : Failed  Requested: Ploeh.AutoFixture.Kernel.SeededRequest
    Requested: CircularReference.Parent
      Requested: System.String Name
        Requested: Ploeh.AutoFixture.Kernel.SeededRequest
          Requested: System.String
          Created: 38ab48f4-b071-40f0-b713-ef9d4c825a85
        Created: Name38ab48f4-b071-40f0-b713-ef9d4c825a85
      Created: Name38ab48f4-b071-40f0-b713-ef9d4c825a85
      Requested: Int32 Age
        Requested: Ploeh.AutoFixture.Kernel.SeededRequest
          Requested: System.Int32
          Created: 9
        Created: 9
      Created: 9
      Requested: CircularReference.Child Child
        Requested: Ploeh.AutoFixture.Kernel.SeededRequest
          Requested: CircularReference.Child
            Requested: System.String Name
              Requested: Ploeh.AutoFixture.Kernel.SeededRequest
                Requested: System.String
                Created: 1f5ca160-b211-4f82-871f-11882dbcf00d
              Created: Name1f5ca160-b211-4f82-871f-11882dbcf00d
            Created: Name1f5ca160-b211-4f82-871f-11882dbcf00d
            Requested: Int32 Age
              Requested: Ploeh.AutoFixture.Kernel.SeededRequest
                Requested: System.Int32
                Created: 120
              Created: 120
            Created: 120
            Requested: CircularReference.Parent Parent
              Requested: CircularReference.Parent
              Created: Ploeh.AutoFixture.Kernel.OmitSpecimen

System.InvalidCastException: Unable to cast object of type 'Ploeh.AutoFixture.Kernel.OmitSpecimen' to type 'CircularReference.Parent'.

【问题讨论】:

    标签: autofixture


    【解决方案1】:

    是否可以使用Customize 方法自定义创建算法?

    如果是,您可以创建并使用以下[ParentChildConventions] 属性:

    internal class ParentChildConventionsAttribute : AutoDataAttribute
    {
        internal ParentChildConventionsAttribute()
            : base(new Fixture().Customize(new ParentChildCustomization()))
        {
        }
    }
    
    internal class ParentChildCustomization : ICustomization
    {
        public void Customize(IFixture fixture)
        {
            fixture.Customize<Child>(c => c
                .With(x => x.Name,
                    fixture.Create<string>().ToLowerInvariant())
                .With(x => x.Age,
                    Math.Min(17, fixture.Create<int>()))
                .Without(x => x.Parent));
    
            fixture.Customize<Parent>(c => c
                .With(x => x.Name,
                    fixture.Create<string>().ToUpperInvariant())
                .With(x => x.Age,
                    Math.Min(18, fixture.Create<int>())));
        }
    }
    

    原始测试,使用[ParentChildConventions] 属性,通过:

    [Theory, ParentChildConventions]
    public void CanCreatePatientGraphWithAutoFixtureManually(
        Parent parent)
    {
        parent.Should().NotBeNull();
        parent.Child.Should().NotBeNull();
        parent.Child.Parent.Should().BeNull();
    }
    

    【讨论】:

    • 很好的解决方案。我想我试图让它变得过于复杂,但我看到了各种解决方案,其中ICustomization 实现只需将一个或多个ISpecimenBuilder 实现添加到夹具的自定义集合中。我是否错过了与您的解决方案相比何时更适合这样做的要点?
    • 通常您会为基础设施目的创建一个ISpecimenBuilder(例如omit properties of a certain kind)。要为特定成员提供特定值,您可以从 Customize 方法开始,并可选择在此过程中创建一个 ISpecimenBuilder
    • 另一件事……我注意到您省略了删除ThrowingRecursionBehavior 和/或添加OmitOnRecursionBehavior 的任何调用,但测试仍然通过。 .Without() 调用是否避免在 ThrowingRecursionBehavior 有机会抛出异常之前检测到此循环引用?
    【解决方案2】:

    您也可以使用AutoFixture.AutoEntityFramework 来帮助 EF。

    【讨论】:

      猜你喜欢
      • 2017-11-09
      • 2021-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-18
      • 2012-04-01
      • 2012-06-20
      相关资源
      最近更新 更多