0

I'm trying to create an object using ISpecimenBuilder:

var fixture= new Fixture();
fixture.Customizations.Add(new SkipNonPrimitiveMembersCustomization());

var person= fixture.Create<Person>(); // works as expected
var node = fixture.Create<Node>(); // only primitive type properties are set

Which is not what I want for the node object. So I tied using FromFactory method:

var fixture = new Fixture();
var node = fixture.Build<Node>()
    .FromFactory(new SkipNonPrimitiveMembersCustomization())
    .Create();

and I got:

Message: AutoFixture.ObjectCreationException : The decorated ISpecimenBuilder could not create a specimen based on the request: SkipNonPrimitiveMembersCustomizationTests+Node. This can happen if the request represents an interface or abstract class; if this is the case, register an ISpecimenBuilder that can create specimens based on the request. If this happens in a strongly typed Build expression, try supplying a factory using one of the IFactoryComposer methods.

Stack Trace: 
NoSpecimenOutputGuard.Create(Object request, ISpecimenContext context)
Postprocessor1.Create(Object request, ISpecimenContext context) CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context)
FilteringSpecimenBuilder.Create(Object request, ISpecimenContext context)
NodeComposer1.Create(Object request, ISpecimenContext context) CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context)
RecursionGuard.Create(Object request, ISpecimenContext context) CompositeNodeComposer1.Create(Object request, ISpecimenContext context)
SpecimenContext.Resolve(Object request) SeedIgnoringRelay.Create(Object request, ISpecimenContext context) CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context)
FilteringSpecimenBuilder.Create(Object request, ISpecimenContext context)
NodeComposer`1.Create(Object request, ISpecimenContext context) CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) RecursionGuard.Create(Object request, ISpecimenContext context)
CompositeNodeComposer1.Create(Object request, ISpecimenContext context)
SpecimenContext.Resolve(Object request)
SpecimenFactory.Create[T](ISpecimenContext context)
SpecimenFactory.Create[T](ISpecimenBuilder builder)
SpecimenFactory.Create[T](IPostprocessComposer1 composer)

My SpecimenBuilder:

public class SkipNonPrimitiveMembersCustomization : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        if (request is PropertyInfo propInfo && propInfo != null && !IsPrimitiveType(propInfo.PropertyType))
            return new OmitSpecimen();

        return new NoSpecimen();
    }

    private static bool IsPrimitiveType(Type type)
    {
        if (type is null) return false;

        return type.IsPrimitive ||
            type.IsValueType ||
            type == typeof(string) ||
            // handle arrays of primitive types
            type.IsArray && IsPrimitiveType(type.GetElementType()) == true ||
            type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>) && type.GenericTypeArguments.All(IsPrimitiveType) ||
            // type is a generic type with value type arguments that are all also primitive types
            type.GetGenericArguments().Any(t => t.IsValueType && IsPrimitiveType(t));
    }
}

private class Node
{
    public string Name { get; set; }
    public int Id { get; set; }
    public string[] Tags { get; set; }
    public TestEnum? TestEnum { get; set; }
    public DateTime? CreatedAt { get; set; }
    public Node[] Children { get; set; }
}

Btw, I know I need to avoid circular reference at the end:

fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
    .ForEach(b => _fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
1
  • 3
    Three backticks for code blocks Commented Oct 5, 2024 at 20:49

1 Answer 1

2

First of all, I would try to put brackets around some conditions to properly group them:

private static bool IsPrimitiveType(Type type)
{
    if (type is null) return false;

    return type.IsPrimitive ||
        type.IsValueType ||
        type == typeof(string) ||
        // handle arrays of primitive types
        (type.IsArray && IsPrimitiveType(type.GetElementType()) == true) ||
        (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>) && type.GenericTypeArguments.All(IsPrimitiveType)) ||
        // type is a generic type with value type arguments that are all also primitive types
        type.GetGenericArguments().Any(t => t.IsValueType && IsPrimitiveType(t));
}

Secondly, you check !IsPrimitiveType(propInfo.PropertyType) which is the opposite what you want I think, corrected version:

public object Create(object request, ISpecimenContext context)
{
    if (request is PropertyInfo propInfo && propInfo != null && IsPrimitiveType(propInfo.PropertyType))
        return new OmitSpecimen();

    return new NoSpecimen();
}

After those corrections, your example worked as expected and it left empty primitive fields.

Sign up to request clarification or add additional context in comments.

3 Comments

+1 Thanks Michal you found a bug in my code. However the problem is Autofixture raising an exception while creating a Node object
@DanielB What exception?
Search for ObjectCreationException in my question

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.