【问题标题】:Autofixture ObjectCreationException - circular reference: RuntimeParameterInfo or Ploeh.AutoFixture.Kernel.SeededRequestAutofixture ObjectCreationException - 循环引用:RuntimeParameterInfo 或 Ploeh.AutoFixture.Kernel.SeedRequest
【发布时间】:2014-01-20 23:01:31
【问题描述】:

我一直在尝试对一些简单的不可变对象进行建模,其中我有两个构造函数重载:一个接受每个只读属性的参数,另一个接受自身的实例进行克隆(以便在大多数属性时使用值将与另一个实例的相同)。

public class Parent {
    public Parent(Child child, Pet pet) {
        Child = child;
        Pet = pet;
    }

    public Parent(Parent template) {
        Child = template.Child;
        Pet = template.Pet;
    }

    public Child Child { get; private set; }
    public Pet Pet { get; private set; }
}

正如我所料,这个测试通过了:

[Theory]
[AutoData]
public void Properties_Equal_Constructor_Arguments(Child expectedChild, Pet expectedPet)
{
    // act
    var sut = new Parent(expectedChild, expectedPet);

    // assert
    sut.Child.Should().Be(expectedChild);
    sut.Pet.Should().Be(expectedPet);
}

但是这个测试抛出了Ploeh.AutoFixture.ObjectCreationException,无法创建System.Reflection.RuntimeParameterInfo类型的实例(使用AutoData参数),或者类型Ploeh.AutoFixture.Kernel.SeededRequest(不使用AutoData)(?!?)

[Theory]
[AutoData]
public void Properties_Equal_Properties_Of_Template_Constructor_Argument(Parent template)
{
    // arrange
    //var fixture = new Fixture();            // yields same error but for type
    //var template = fixture.Create<Parent>();// Ploeh.AutoFixture.Kernel.SeededRequest
    var expectedChild = template.Child;
    var expectedPet = template.Pet;

    // act
    var sut = new Parent(template);

    // assert
    sut.Child.Should().Be(expectedChild);
    sut.Pet.Should().Be(expectedPet);
}

首先,我没有在我的类中发现任何循环引用,就像您通常在有关子对象对其父对象具有属性引用的问题中看到的那样(就像有时在数据库实体建模中发生的那样)。其次,当我过去收到此错误时,它指的是对我的类中明确使用的类型的循环引用,与本例不同。

为什么会这样?

为了完整起见,以下是其他相关类和收到的完整错误消息:

public class Child {
    public Child(string value) {
        Value = value;
    }
    public string Value { get; private set; }
}

public class Pet {
    public Pet(string name) {
        Name = name;
    }
    public string Name { get; private set; }
}

System.InvalidOperationException 获取时抛出异常 理论数据 AutoFixtureTests.CircularReferenceTests.Properties_Equal_Properties_Of_Template_Constructor_Argument: Ploeh.AutoFixture.ObjectCreationException:AutoFixture 无法 创建一个 System.Reflection.RuntimeParameterInfo 类型的实例 因为遍历的对象图包含循环引用。 有关圆形路径的信息如下。这是正确的 当 Fixture 配备 ThrowingRecursionBehavior 时的行为, 这是默认设置。这可确保您了解 代码中的循环引用。你的第一反应应该是 重新设计您的 API 以摆脱所有循环引用。 但是,如果这是不可能的(很可能是因为部分或全部 API由第三方交付),您可以替换此默认值 具有不同行为的行为:在 Fixture 实例上,删除 来自 Fixture.Behaviors 的 ThrowingRecursionBehavior,而是添加 OmitOnRecursionBehavior 的一个实例。小路: AutoFixtureTests.Parent 模板 --> AutoFixtureTests.Parent --> AutoFixtureTests.Parent 模板 --> Ploeh.AutoFixture.Kernel.SeedRequest

在 Ploeh.AutoFixture.Kernel.ThrowingRecursionHandler.HandleRecursiveRequest(对象 请求,IEnumerable1 recordedRequests) at Ploeh.AutoFixture.Kernel.RecursionGuard.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.Postprocessor1.Create(对象请求, ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象 请求,ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.TracingBuilder.Create(对象请求, ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.TerminatingWithPathSpecimenBuilder.Create(对象 请求,ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.RecursionGuard.Create(对象请求, ISpecimenContext 上下文)在 System.Linq.Enumerable.WhereSelectArrayIterator2.MoveNext() at System.Collections.Generic.List1..ctor(IEnumerable1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable
1 源) 在 Ploeh.AutoFixture.Kernel.MethodInvoker.Create(对象请求, ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象 请求,ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象 请求,ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.Postprocessor1.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.TracingBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.TerminatingWithPathSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.RecursionGuard.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.Postprocessor1.Create(对象请求, ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象 请求,ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.TracingBuilder.Create(对象请求, ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.TerminatingWithPathSpecimenBuilder.Create(对象 请求,ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.RecursionGuard.Create(对象请求, ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象 请求,ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象 请求,ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.Postprocessor`1.Create(对象请求, ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象 请求,ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.TracingBuilder.Create(对象请求, ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.TerminatingWithPathSpecimenBuilder.Create(对象 请求,ISpecimenContext 上下文)在 Ploeh.AutoFixture.Kernel.RecursionGuard.Create(对象请求, ISpecimenContext 上下文)在 Ploeh.AutoFixture.Xunit.AutoDataAttribute.GetData(MethodInfo methodUnderTest, Type[] parameterTypes) 在 Xunit.Extensions.TheoryAttribute.d_7.MoveNext() 在 Xunit.Extensions.TheoryAttribute.EnumerateTestCommands(IMethodInfo 方法)在 Xunit.Extensions.TheoryAttribute.c_DisplayClass5.b__1() 在 Xunit.Extensions.TheoryAttribute.LambdaTestCommand.Execute(Object testClass)

以及不使用 AutoDataAttribute 时:

Ploeh.AutoFixture.ObjectCreationException AutoFixture 无法 创建一个 Ploeh.AutoFixture.Kernel.SeedRequest 类型的实例 因为遍历的对象图包含循环引用。 有关圆形路径的信息如下。这是正确的 当 Fixture 配备 ThrowingRecursionBehavior 时的行为, 这是默认设置。这可确保您了解 代码中的循环引用。你的第一反应应该是 重新设计您的 API 以摆脱所有循环引用。 但是,如果这是不可能的(很可能是因为部分或全部 API由第三方交付),您可以替换此默认值 具有不同行为的行为:在 Fixture 实例上,删除 来自 Fixture.Behaviors 的 ThrowingRecursionBehavior,而是添加 OmitOnRecursionBehavior 的一个实例。小路: AutoFixtureTests.Parent 模板 --> AutoFixtureTests.Parent --> AutoFixtureTests.Parent 在 Ploeh.AutoFixture.Kernel.ThrowingRecursionHandler.HandleRecursiveRequest(对象请求,IEnumerable1 recordedRequests) at Ploeh.AutoFixture.Kernel.RecursionGuard.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.Postprocessor1.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.TracingBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.TerminatingWithPathSpecimenBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.RecursionGuard.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.Postprocessor1.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.TracingBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.TerminatingWithPathSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.RecursionGuard.Create(Object request, ISpecimenContext context) at System.Linq.Enumerable.WhereSelectArrayIterator2.MoveNext() 在 System.Collections.Generic.List1..ctor(IEnumerable1 集合)
在 System.Linq.Enumerable.ToList(IEnumerable1 source) at Ploeh.AutoFixture.Kernel.MethodInvoker.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.Postprocessor1.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.TracingBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.TerminatingWithPathSpecimenBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.RecursionGuard.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.Postprocessor`1.Create(Object request, ISspecimenContext context) 在 Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.TracingBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.TerminatingWithPathSpecimenBuilder.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.Kernel.RecursionGuard.Create(对象请求,ISpecimenContext 上下文) 在 Ploeh.AutoFixture.SpecimenFactory.Create(ISpecimenContext context, T seed) 在 Ploeh.AutoFixture.SpecimenFactory.Create(ISpecimenContext 上下文) 在 AutoFixtureTests.CircularReferenceTests.Properties_Equal_Properties_Of_Template_Constructor_Argument() 在 AutoFixtureTests.cs:第 28 行

【问题讨论】:

  • 循环引用就在这里:public Parent(Parent template).
  • @MarkSeemann 真的!到目前为止,我看到的示例中,Child 属性在其属性中引用了它自己的Parent。我认为,因为只要求 AF 创建一个 Parent 实例(我正在创建另一个),所以没问题。但只是在创建第一个时,它必须一次又一次地为构造函数创建另一个。完全有道理。如果我使用GreedyConstructorQuery,我将看看这是否会消失,所以它会选择另一个构造函数(带有两个参数)。在这种情况下,AF 是在指出“气味”吗?一如既往,非常感谢,马克。

标签: c# unit-testing xunit.net autofixture


【解决方案1】:

对于任何试图避免由于实体框架和虚拟成员而导致循环引用的人,这里有一个自定义项,可以在生成模拟数据时忽略它们。

public class IgnoreVirtualMembers : ISpecimenBuilder
{
    public Type ReflectedType { get; }

    public object Create(object request, ISpecimenContext context)
    {
        var pi = request as PropertyInfo;
        if (pi != null) //// is a property
        {
            if (this.ReflectedType == null || //// is hosted anywhere
                this.ReflectedType == pi.ReflectedType) //// is hosted in defined type
            {
                if (pi.GetGetMethod().IsVirtual)
                {
                    return new OmitSpecimen();
                }
            }
        }

        return new NoSpecimen();
    }
}

public class IgnoreVirtualMembersCustomisation : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(new IgnoreVirtualMembers());
    }
}

随用法:

new Fixture().Customize(new IgnoreVirtualMembersCustomisation());

【讨论】:

  • 这是使用实体框架时 autofixture 抱怨的循环引用错误的救命稻草。谢谢你。顺便问一下,当使用 AutoMoqData 代替夹具时,您有类似的解决方案吗?
  • 对不起,没用过
  • 感谢您的回复。我用你的解决方案让它工作了。
猜你喜欢
  • 1970-01-01
  • 2014-09-01
  • 2021-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多