Search code examples
c#autofixture

How to create random concrete implementation of type


I am cleaning up arrange phase of my tests and AutoFixture was great help but I am struggling with common pattern I have throughout my tests. I have inheritance structure which starts with more than one interface or abstract class from which then concrete types are derived and I would like to create some concrete implementation of the base type I don't care which one. I know there are type relays but I don't think they allow for random behavior I want. I guess AutoFixture would need to know about the type hierarchy somehow to be able to pick concrete implementation.

AutoFixture.AutoMoq is included so you know I have access to it.

I just installed AutoFixture so please don't be restricted with my approach feel free to provide your own solution.

using System;
using System.Diagnostics;
using System.Linq;
using AutoFixture;
using AutoFixture.AutoMoq;

// ARRANGE
var fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());

// ACT
// I want this to be filled with random combination of non abstract implementations of `Command`
var commands  = fixture.CreateMany<Command>(3).ToList();

// ASSERT
// Fails for any value of count `CreateMany(int count)`
Debug.Assert(commands.Any(x => x.Type != CommandType.CREATE_FOO));

public enum CommandType { CREATE_FOO, DELETE_FOO }
 
public abstract class Command
{
  public CommandType Type { get; set; }
}

  public abstract class CreateCommand : Command { }

    public class CreateFoo : CreateCommand
    {
      public CreateFoo() => Type = CommandType.CREATE_FOO;
    }

  public abstract class DeleteCommand : Command { }

    public class DeleteFoo : DeleteCommand
    {
      public DeleteFoo() => Type = CommandType.DELETE_FOO;
    }

Solution

  • I'll explain a simpler scenario since for the purpose of brevity. I'm sure you can extrapolate it and use the solution in your tests.

    Let's say that the level of nesting is just one you have a base type of Command and two child types CreateFoo and DeleteFoo. You want to randomly crate either one or another.

    What you can do is create a builder class similar to a type relay, but which instead of relaying your request directly will pick a random relay and then will invoke that.

    Your test should look something like this.

    [Fact]
    public void GeneratesRandomCommands()
    {
        var fixture = new Fixture();
        fixture.Customizations.Add(
            new FilteringSpecimenBuilder(
                new RandomRelayCustomization(
                    new TypeRelay(typeof(Command), typeof(DeleteFoo)),
                    new TypeRelay(typeof(Command), typeof(CreateFoo))),
                new ExactTypeSpecification(typeof(Command))));
    
        var items = fixture.CreateMany<Command>(10);
    
        items.OfType<DeleteFoo>().Should().HaveCount(5);
        items.OfType<CreateFoo>().Should().HaveCount(5);
    }
    

    Notice the RandomRelayCustomization which takes a collection of relays. That's the class that would pick the random relay.

    In AutoFixture we use usually use pseudo-randomness since that's faster and usually tests don't require true randomness. I'm sure you can expand the idea and make it as random as you need. For this example I used a RoundRobinSequence which is just an IEnumerable<int> that loops on a sequence of indices, skipping every second index. You can see the full example for this builder in this Gist.

    public class RandomRelayCustomization : ISpecimenBuilder
    {
        private readonly List<ISpecimenBuilder> builders;
        private readonly IEnumerator<int> randomizer;
    
        public RandomRelayCustomization(params ISpecimenBuilder[] builders)
            : this(builders.AsEnumerable())
        {
        }
    
        public RandomRelayCustomization(IEnumerable<ISpecimenBuilder> builders)
        {
            if (builders is null)
            {
                throw new ArgumentNullException(nameof(builders));
            }
    
            this.builders = builders.ToList();
            this.randomizer = new RoundRobinSequence(0, this.builders.Count - 1)
                .GetEnumerator();
        }
    
        public object Create(object request, ISpecimenContext context)
        {
            this.randomizer.MoveNext();
            var builder = this.builders[this.randomizer.Current];
            return builder.Create(request, context);
        }
    }
    

    Since you don't want this build invoked with requests other than your base type, I added a FilteringSpecimenBuilder that filters incoming requests using the ExactTypeSpecification so the relay would work only when you receive a base type request of Command.

    Let me know if this helps.