Search code examples
c#reflectionautofixture

Build Autofixture Customization Using Reflection


I'm trying to create an AutoFixture.ICustomization that loops over every type that inherits from my model base and then explicitly doesn't assign any values to properties that also derive from that model base.

Obviously, this requires some serious abuse of reflection.

So far, I've got this:

public class DiscosModelFixtureCustomizationNoLinks: ICustomization
{
    public void Customize(IFixture fixture)
    {
        Type[]     discosModelTypes = typeof(DiscosModelBase).Assembly.GetTypes().Where(t => t.IsDiscosModel() && !t.IsAbstract).ToArray();
        MethodInfo customizeMethod  = fixture.GetType().GetMethods().Single(m => m.Name == nameof(fixture.Customize) && m.IsGenericMethod); // Needed to resolve overload with generic
        
        foreach (Type t in discosModelTypes)
        {
            MethodInfo     constructedCustomizeMethod = customizeMethod.MakeGenericMethod(t);
            Type           customizationComposer      = typeof(ICustomizationComposer<>).MakeGenericType(t);
            PropertyInfo[] propsToIgnore              = t.GetProperties().Where(p => p.PropertyType.IsDiscosModel()).ToArray();
            foreach (PropertyInfo prop in propsToIgnore)
            {
                // I want to essentially do this
                // For every prop that derives from DiscosModelBase
                // fixture.Customize<DiscosObject>(c => c.Without(p => p.Id));
                
                // Using my constructed method
                constructedCustomizeMethod.Invoke(fixture, new [] {myExpression});
            }
        }
    }
}

So .Customize<T>() has this signature:

void Customize<T>(Func<ICustomizationComposer<T>, ISpecimenBuilder> composerTransformation);

And ICustomizationComposer<T>.Without() has this signature:

IPostprocessComposer<T> Without<TProperty>(Expression<Func<T, TProperty>> propertyPicker);

So essentially, I need to work out how to build the equivalent of c => c.Without(p => p.Id) using reflection and my PropertyInfo.

This answer would indicated that I can manually build the expression tree, however, it's not really explained in enough detail that I'm able to apply it in this context.

First up, is this possible? Secondly, if so, how do I accomplish this missing step?


Solution

  • So it seems as though I was massively overcomplicating this. Following some assistance from the dev behind AutoFixture, it's been made clear that what I'm trying to accomplish can be done through the use of an Omitter.

    This Omitter requires a PropertySpecification which requires an instance of IEquatable<PropertyInfo> to tell it which properties to ignore.

    So putting all of that together inside a customizer gives us:

    public class DiscosModelFixtureCustomizationNoLinks: ICustomization
    {
        public void Customize(IFixture fixture)
        {
            var omitter = new Omitter(new PropertySpecification(new DiscosModelPropertyComparer()));
            fixture.Customizations.Add(omitter);
        }
        
        private class DiscosModelPropertyComparer : IEquatable<PropertyInfo>
        {
            public bool Equals(PropertyInfo other) => other.PropertyType.IsDiscosModel();
        }
    }
    

    For completeness, IsDiscosModel() is:

    public static class TypeExtensions
    {
        public static bool IsDiscosModel(this Type t) =>
            t.IsAssignableTo(typeof(DiscosModelBase))                                           ||
            t.HasElementType     && t.GetElementType()!.IsAssignableTo(typeof(DiscosModelBase)) ||
            t.IsCollectionType() && t.IsGenericType && t.GetGenericArguments().Single().IsAssignableTo(typeof(DiscosModelBase));
    }
    
    

    Extra Info

    It's worth noting that, while I didn't need it, the linked discussion shows how PropertySpecifications can be combined together with and and or statements to develop complex conditions for Omitters. For instance, if we take a type comparer that matches a given base type:

    public class DeclaringTypeComparer : IEquatable<PropertyInfo>
    {
        public DeclaringTypeComparer(Type declaringType)
        {
            this.DeclaringType = declaringType;
        }
    
        public Type DeclaringType { get; }
    
        public bool Equals(PropertyInfo other)
        {
            return other.DeclaringType == this.DeclaringType;
        }
    }
    

    This can then be combined to ignore all properties inherited from base type and one specific property of a child type.

        var spec = new OrRequestSpecification(
            new PropertySpecification(new DeclaringTypeComparer(typeof(BaseType))),
            new AndRequestSpecification(
                new PropertySpecification(
                    new DeclaringTypeComparer(typeof(ChildType))),
                new PropertySpecification(
                    new PropertyTypeAndNameCriterion(
                        new Criterion<Type>(typeof(IReadOnlyList<int>), EqualityComparer<Type>.Default),
                        new Criterion<string>("Numbers", StringComparer.Ordinal)))));
        var omitter = new Omitter(spec);
        var fixture = new Fixture();
        fixture.Customizations.Insert(0, omitter);