Jak mogę używać niestandardowych programów ISpecimenBuilders z OmitOnRecursionBehavior?

Jak mogę użyć niestandardowegoISpecimenBuilder instancje wraz zOmitOnRecursionBehavior które chcę zastosować globalnie do wszystkich obiektów tworzonych przez urządzenia?

Pracuję z modelem EF Code First z cuchnącym odniesieniem kolistym, którego na potrzeby tego pytania nie można wyeliminować:

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

Jestem zaznajomiony z techniką odwołań cyklicznych, jak w tym teście:

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

Ale jeśli którekolwiek z obu dostosowań nie zostaną skomentowane, otrzymam wyjątek:

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

Upadająca obsada pojawia się w moimISpecimenBuilder implementacje (pokazane na dole tego pytania), gdy dzwonię naISpecimenContext rozwiązaćParent a prośba pochodzi odChild rozwiązany. Mógłbym wystrzegać się żądania pochodzącego odChild rozwiązywanie operacji w ten sposób:

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

Ale to wydaje się zanieczyszczaćISpecimenBuilder wdrożenie z wiedzą o tym, kto może złożyć wniosek. Wydaje się również, że powtarza pracę, którą „globalny”OmitOnRecursionBehavior ma to zrobić.

Chcę użyćISpecimenBuilder instancje, ponieważ mam inne rzeczy do dostosowania poza obsługą odwołania cyklicznego. Spędziłem dużo czasu szukając przykładów takiego scenariusza tutaj na SO, a także naPloeh ale nie znalazłem jeszcze niczego, co omawiapołączenie zachowań i dostosowań. Ważne jest, aby rozwiązanie było takie, z którym mogę się zająćICustomization, zamiast linii i linii w konfiguracji testu

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

... ponieważ ostatecznie chcę przedłużyć[AutoData] atrybut do testów.

Co następuje, to mojeISpecimenBuilder implementacje i wyjścieTracingBehavior w przypadku niepowodzenia testu:

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

questionAnswers(2)

yourAnswerToTheQuestion