Controlling the depth of a tree of objects using Autofixture

I am trying to control the depth of a tree of objects using Autofixture. In some cases, I just want to create a root object, and in another set of cases I can generate a tree to a certain depth (suppose 2, 3).

class Foo { public string Name {get;set;} public Bar Bar {get;set;} public AnotherType Xpto {get;set;} public YetAnotherType Xpto {get;set;} } class Bar { public string Name {get;set;} public string Description {get;set;} public AnotherType Xpto {get;set;} public YetAnotherType Xpto {get;set;} public Xpto Xpto {get;set;} } class Xpto { public string Description {get;set;} public AnotherType Xpto {get;set;} public YetAnotherType Xpto {get;set;} } 

In the above example, I would like (depth 1) to control the generation process to create an instance of only the Foo class and the Bar property or any other reference type in this class is not populated or (depth 2) I would like to create an instance of the Foo class, the Bar property is populated with a new instance of Bar, but the Xpto property or any other reference type in this class is not populated.

In case I didn't notice it in the code base, does Autofixture have a setting or behavior to allow us to have that kind of control?

Again, this is not a recursion that I want to control, but the depth of the population of the graph objects.

+10
source share
3 answers

No bar

One-time:

 var f = fixture.Build<Foo>().Without(f => f.Bar).Create(); 

Reusable:

 fixture.Customize<Foo>(c => c.Without(f => f.Bar)); var f = fixture.Create<Foo>(); 

No xpto

One-time:

 var f = fixture .Build<Foo>() .With( f => f.Bar, fixture.Build<Bar>().Without(b => b.Xpto).Create()) .Create(); 

Reusable:

 fixture.Customize<Bar>(c => c.Without(b => b.Xpto)); var f = fixture.Create<Foo>(); 
+7
source

Here you go GenerationDepthBehavior

fixture.Behaviors.Add (new GenerationDepthBehavior (2));

 public class GenerationDepthBehavior : ISpecimenBuilderTransformation { private const int DefaultGenerationDepth = 1; private readonly int generationDepth; public GenerationDepthBehavior() : this(DefaultGenerationDepth) { } public GenerationDepthBehavior(int generationDepth) { if (generationDepth < 1) throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0."); this.generationDepth = generationDepth; } public ISpecimenBuilderNode Transform(ISpecimenBuilder builder) { if (builder == null) throw new ArgumentNullException(nameof(builder)); return new GenerationDepthGuard(builder, new GenerationDepthHandler(), this.generationDepth); } } public interface IGenerationDepthHandler { object HandleGenerationDepthLimitRequest(object request, IEnumerable<object> recordedRequests, int depth); } public class DepthSeededRequest : SeededRequest { public int Depth { get; } public int MaxDepth { get; set; } public bool ContinueSeed { get; } public int GenerationLevel { get; private set; } public DepthSeededRequest(object request, object seed, int depth) : base(request, seed) { Depth = depth; Type innerRequest = request as Type; if (innerRequest != null) { bool nullable = Nullable.GetUnderlyingType(innerRequest) != null; ContinueSeed = nullable || innerRequest.IsGenericType; if (ContinueSeed) { GenerationLevel = GetGenerationLevel(innerRequest); } } } private int GetGenerationLevel(Type innerRequest) { int level = 0; if (Nullable.GetUnderlyingType(innerRequest) != null) { level = 1; } if (innerRequest.IsGenericType) { foreach (Type generic in innerRequest.GetGenericArguments()) { level++; level += GetGenerationLevel(generic); } } return level; } } public class GenerationDepthGuard : ISpecimenBuilderNode { private readonly ThreadLocal<Stack<DepthSeededRequest>> requestsByThread = new ThreadLocal<Stack<DepthSeededRequest>>(() => new Stack<DepthSeededRequest>()); private Stack<DepthSeededRequest> GetMonitoredRequestsForCurrentThread() => this.requestsByThread.Value; public GenerationDepthGuard(ISpecimenBuilder builder) : this(builder, EqualityComparer<object>.Default) { } public GenerationDepthGuard( ISpecimenBuilder builder, IGenerationDepthHandler depthHandler) : this( builder, depthHandler, EqualityComparer<object>.Default, 1) { } public GenerationDepthGuard( ISpecimenBuilder builder, IGenerationDepthHandler depthHandler, int generationDepth) : this( builder, depthHandler, EqualityComparer<object>.Default, generationDepth) { } public GenerationDepthGuard(ISpecimenBuilder builder, IEqualityComparer comparer) { this.Builder = builder ?? throw new ArgumentNullException(nameof(builder)); this.Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); this.GenerationDepth = 1; } public GenerationDepthGuard( ISpecimenBuilder builder, IGenerationDepthHandler depthHandler, IEqualityComparer comparer) : this( builder, depthHandler, comparer, 1) { } public GenerationDepthGuard( ISpecimenBuilder builder, IGenerationDepthHandler depthHandler, IEqualityComparer comparer, int generationDepth) { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (depthHandler == null) throw new ArgumentNullException(nameof(depthHandler)); if (comparer == null) throw new ArgumentNullException(nameof(comparer)); if (generationDepth < 1) throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0."); this.Builder = builder; this.GenerationDepthHandler = depthHandler; this.Comparer = comparer; this.GenerationDepth = generationDepth; } public ISpecimenBuilder Builder { get; } public IGenerationDepthHandler GenerationDepthHandler { get; } public int GenerationDepth { get; } public int CurrentDepth { get; } public IEqualityComparer Comparer { get; } protected IEnumerable RecordedRequests => this.GetMonitoredRequestsForCurrentThread(); public virtual object HandleGenerationDepthLimitRequest(object request, int currentDepth) { return this.GenerationDepthHandler.HandleGenerationDepthLimitRequest( request, this.GetMonitoredRequestsForCurrentThread(), currentDepth); } public object Create(object request, ISpecimenContext context) { if (request is SeededRequest) { int currentDepth = 0; var requestsForCurrentThread = GetMonitoredRequestsForCurrentThread(); if (requestsForCurrentThread.Count > 0) { currentDepth = requestsForCurrentThread.Max(x => x.Depth) + 1; } DepthSeededRequest depthRequest = new DepthSeededRequest(((SeededRequest)request).Request, ((SeededRequest)request).Seed, currentDepth); if (depthRequest.Depth >= GenerationDepth) { var parentRequest = requestsForCurrentThread.Peek(); depthRequest.MaxDepth = parentRequest.Depth + parentRequest.GenerationLevel; if (!(parentRequest.ContinueSeed && currentDepth < depthRequest.MaxDepth)) { return HandleGenerationDepthLimitRequest(request, depthRequest.Depth); } } requestsForCurrentThread.Push(depthRequest); try { return Builder.Create(request, context); } finally { requestsForCurrentThread.Pop(); } } else { return Builder.Create(request, context); } } public virtual ISpecimenBuilderNode Compose( IEnumerable<ISpecimenBuilder> builders) { var composedBuilder = ComposeIfMultiple( builders); return new GenerationDepthGuard( composedBuilder, this.GenerationDepthHandler, this.Comparer, this.GenerationDepth); } internal static ISpecimenBuilder ComposeIfMultiple(IEnumerable<ISpecimenBuilder> builders) { ISpecimenBuilder singleItem = null; List<ISpecimenBuilder> multipleItems = null; bool hasItems = false; using (var enumerator = builders.GetEnumerator()) { if (enumerator.MoveNext()) { singleItem = enumerator.Current; hasItems = true; while (enumerator.MoveNext()) { if (multipleItems == null) { multipleItems = new List<ISpecimenBuilder> { singleItem }; } multipleItems.Add(enumerator.Current); } } } if (!hasItems) { return new CompositeSpecimenBuilder(); } if (multipleItems == null) { return singleItem; } return new CompositeSpecimenBuilder(multipleItems); } public virtual IEnumerator<ISpecimenBuilder> GetEnumerator() { yield return this.Builder; } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } public class GenerationDepthHandler : IGenerationDepthHandler { public object HandleGenerationDepthLimitRequest( object request, IEnumerable<object> recordedRequests, int depth) { return new OmitSpecimen(); } } 
+3
source

This feature was requested on GitHub. This was ultimately rejected. However, it was rejected because a good simple solution was posted as part of this problem.

  public class GenerationDepthBehavior: ISpecimenBuilderTransformation { public int Depth { get; } public GenerationDepthBehavior(int depth) { Depth = depth; } public ISpecimenBuilderNode Transform(ISpecimenBuilder builder) { return new RecursionGuard(builder, new OmitOnRecursionHandler(), new IsSeededRequestComparer(), Depth); } private class IsSeededRequestComparer : IEqualityComparer { bool IEqualityComparer.Equals(object x, object y) { return x is SeededRequest && y is SeededRequest; } int IEqualityComparer.GetHashCode(object obj) { return obj is SeededRequest ? 0 : EqualityComparer<object>.Default.GetHashCode(obj); } } } 

https://github.com/AutoFixture/AutoFixture/issues/1032#issuecomment-385928866

0
source

Source: https://habr.com/ru/post/957980/


All Articles