Wie kann ich benutzerdefinierte ISpecimenBuilder mit OmitOnRecursionBehavior verwenden?

Wie kann ich benutzerdefinierte verwendenISpecimenBuilder Instanzen zusammen mit derOmitOnRecursionBehavior Was möchte ich global auf alle Fixture-Objekte anwenden?

Ich arbeite mit einem EF Code First-Modell mit einem übelriechenden Zirkelverweis, der für die Zwecke dieser Frage nicht beseitigt werden kann:

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; }
}

Ich kenne mich mit der Technik für Zirkelverweise aus, wie in diesem bestandenen Test:

[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();
}

Wenn eine oder beide Anpassungen nicht kommentiert sind, erhalte ich eine Ausnahme:

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

Die fehlgeschlagene Besetzung tritt in meinerISpecimenBuilder Implementierungen (am Ende dieser Frage gezeigt), wenn ich auf dieISpecimenContext lösenParent und die Anfrage kommt von derChild gelöst werden. Ich könnte mich gegen die Bitte von der schützenChild Vorgang wie folgt lösen:

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

Aber das scheint die Umwelt zu verschmutzenISpecimenBuilder Implementierung mit dem Wissen, von wem die Anfrage stammt. Auch scheint es die Arbeit zu duplizieren, die die "globale"OmitOnRecursionBehavior soll tun.

Ich möchte das benutzenISpecimenBuilder Instanzen, weil ich neben der Zirkelreferenz noch andere Anpassungen vornehmen muss. Ich habe viel Zeit damit verbracht, nach Beispielen für ein solches Szenario hier auf SO und auch auf zu suchenPloeh aber ich habe noch nichts gefunden, das das besprichtKombination von Verhalten und Anpassungen. Es ist wichtig, dass die Lösung eine ist, mit der ich kapseln kannICustomization, anstatt Zeilen und Zeilen im Testaufbau von

//...
fixture.ActLikeThis(new SpecialBehavior())
       .WhenGiven(typeof (Parent))
       .AndDoNotEvenThinkAboutBuilding(typeof(Child))
       .UnlessParentIsNull()
//...

... weil ich letztendlich einen verlängern will[AutoData] Attribut für Tests.

Was folgt, sind meineISpecimenBuilder Implementierungen und die Ausgabe derTracingBehavior für den nicht bestandenen Test:

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'.

Antworten auf die Frage(2)

Ihre Antwort auf die Frage