I need to enable AutoFixture to create instances of types with circular references (from an API provided by a third party). To do this I can remove the default ThrowingRecursionBehavior
as shown below:
public class RecursiveObjectCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Behaviors.OfType<ThrowingRecursionBehavior>()
.ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
}
}
However, I understand that this will remove the ThrowingRecursionBehavior
for all types when the customization is applied via an attribute. How can I limit the modified behavior to only apply to the specific types?
You'll have to create a custom behavior for that.
Here is something to start on:
public class OmitOnRecursionForRequestBehavior : ISpecimenBuilderTransformation
{
private const int DefaultRecursionDepth = 1;
private readonly int recursionDepth;
private readonly object request;
public OmitOnRecursionForRequestBehavior(object request)
: this(request, DefaultRecursionDepth)
{
}
public OmitOnRecursionForRequestBehavior(
object request,
int recursionDepth)
{
if (request == null)
throw new ArgumentNullException("request");
if (recursionDepth < 1)
throw new ArgumentOutOfRangeException(
"recursionDepth",
"Recursion depth must be greater than 0.");
this.recursionDepth = recursionDepth;
this.request = request;
}
public ISpecimenBuilder Transform(ISpecimenBuilder builder)
{
if (builder == null)
throw new ArgumentNullException("builder");
return new RecursionGuard(
builder,
new RecursionForRequestHandler(
request,
new OmitOnRecursionHandler(),
builder),
recursionDepth);
}
}
public class RecursionForRequestHandler : IRecursionHandler
{
private readonly object request;
private readonly IRecursionHandler handlerForRequest;
private readonly ISpecimenBuilder handler;
public RecursionForRequestHandler(
object request,
IRecursionHandler handlerForRequest,
ISpecimenBuilder handler)
{
if (request == null)
throw new ArgumentNullException("request");
if (handlerForRequest == null)
throw new ArgumentNullException("handlerForRequest");
if (handler == null)
throw new ArgumentNullException("handler");
this.request = request;
this.handlerForRequest = handlerForRequest;
this.handler = handler;
}
public object HandleRecursiveRequest(
object request,
IEnumerable<object> recordedRequests)
{
if (this.request.Equals(request))
return handlerForRequest.HandleRecursiveRequest(
request,
recordedRequests);
return handler.Create(request, new SpecimenContext(handler));
}
}
This is how you would use it:
fixture.Behaviors.Add(new OmitOnRecursionForRequestBehavior(typeof(MyType)));
fixture.Behaviors.Add(new OmitOnRecursionForRequestBehavior(typeof(AnotherType)));
Note that you don't remove the ThrowingRecursionBehavior
as it will be used to guard the other requests, otherwise a StackOverflowException
will be thrown.
However, if you specify a recursionDepth
greater than 1, you'll have to remove the ThrowingRecursionBehavior
and create a customized one with a greater or equal recursionDepth
.
public class DepthThrowingRecursionBehavior : ISpecimenBuilderTransformation
{
private readonly int recursionDepth;
public DepthThrowingRecursionBehavior(int recursionDepth)
{
if (recursionDepth < 1)
throw new ArgumentOutOfRangeException(
"recursionDepth",
"Recursion depth must be greater than 0.");
this.recursionDepth = recursionDepth;
}
public ISpecimenBuilder Transform(ISpecimenBuilder builder)
{
if (builder == null)
throw new ArgumentNullException("builder");
return new RecursionGuard(
builder,
new ThrowingRecursionHandler(),
recursionDepth);
}
}
fixture.Behaviors.OfType<ThrowingRecursionBehavior>()
.ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new DepthThrowingRecursionBehavior(2));
fixture.Behaviors.Add(new OmitOnRecursionForRequestBehavior(typeof(MyType), 2));
fixture.Behaviors.Add(new OmitOnRecursionForRequestBehavior(typeof(AnotherType), 1));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With