Search code examples
.netgenericsautofixtureopen-generics

How can I register a generic object factory?


I have the following two classes:

public class KeyedEntity<TEntity>
{
    internal KeyedEntity() { }

    public Identifier Key { get; set; }
    public TEntity Entity { get; set; }
}

public static class KeyedEntity
{
    public static KeyedEntity<TEntity> Create<TEntity>(Identifier key, TEntity entity)
    {
        return new KeyedEntity<TEntity>
        {
            Key = key,
            Entity = entity,
        };
    }
}

The reason the constructor is internal and the second class exists is I want to enforce the more highly-maintainable KeyedEntity.Create(x, y) syntax rather than new KeyedEntity<T>{ Key = x, Entity = y }. (Note that the type is inferred with the former syntax.)

I want to tell AutoFixture how to create an instance of KeyedEntity. However, the Register method only seems to allow registration of a single type rather than an open generic type.

How can I register KeyedEntity.Create<TEntity> as the creation function for KeyedEntity<TEntity>?


Solution

  • To support your open generic type, you can write a custom Specimen Builder:

    public class KeyedEntityBuilder : ISpecimenBuilder
    {
        private readonly static MethodInfo createMethod =
            typeof(KeyedEntity).GetMethod("Create");
    
        public object Create(object request, ISpecimenContext context)
        {
            var t = request as Type;
            if (t == null ||
                !t.IsGenericType ||
                t.GetGenericTypeDefinition() != typeof(KeyedEntity<>))
                return new NoSpecimen(request);
    
            var entityType = t.GetGenericArguments().Single();
    
            var key = context.Resolve(typeof(Identifier));
            var entity = context.Resolve(entityType);
    
            return createMethod
                .MakeGenericMethod(entityType)
                .Invoke(null, new[] { key, entity });
        }
    }
    

    (Defensive coding omitted for clarity.)

    The following unit test passes:

    public class Tests
    {
        [Fact]
        public void CreateKeyedEntity()
        {
            var fixture = new Fixture();
            fixture.ResidueCollectors.Add(new KeyedEntityBuilder());
    
            var actual = fixture.Create<KeyedEntity<Foo>>();
    
            Assert.NotNull(actual.Key);
            Assert.NotNull(actual.Entity);
        }
    }
    

    For better maintainability, you should encapsulate KeyedEntityBuilder in a Customization.