reggaeguitar
reggaeguitar

Reputation: 1804

Random enum generation

I'd like AutoFixture to use a random value of an enum whenever that enum is included in a type I'm trying to create. Basically the same as this https://github.com/AutoFixture/AutoFixture/issues/360 but for enums.

I tried the following but AutoFixture tries creating the enum instead of the type requested and can't cast it.

public class RandomEnumSequenceGenerator<T> : ISpecimenBuilder where T : struct
{
    private static Random _random = new Random();
    private Array _values;

    public RandomEnumSequenceGenerator()
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("T must be an enum");
        }
        _values = Enum.GetValues(typeof(T));
    }

    public object Create(object request, ISpecimenContext context)
    {
        var index = _random.Next(0, _values.Length - 1);
        return _values.GetValue(index);
    }
}

I then use it in my BaseUnitTest class like this

    public class BaseUnitTestClass
    {
        internal static Fixture _fixture = new Fixture();

        public BaseUnitTestClass()
        {               
            _fixture.Customizations.Add(new RandomEnumSequenceGenerator<TableType>());
        }

I pulled down the source code and noticed that it loops through the composedBuilders and only the last one (the RandomEnumSequenceGenerator) satisfies the contract, it then creates a TableType enum value and tries to cast it as the actual class I'm trying to create which throws an exception.

The exception message is as follows

at Ploeh.AutoFixture.SpecimenFactory.Create[T](ISpecimenContext context, T seed) at Ploeh.AutoFixture.SpecimenFactory.Create[T](ISpecimenContext context) at Ploeh.AutoFixture.SpecimenFactory.Create[T](ISpecimenBuilder builder) at UnitTests.Unit.BaseUnitTestClass.GetRandomT in mypath\BaseUnitTestClass.cs:line 49 Result Message: System.InvalidCastException : Unable to cast object of type 'MyNamespace.TableType' to type 'MyNameSpace.AssumptionChangeCriteria'.

AssumptionChangeCriteria has a property of type TableType which is an enum.

where GetRandom<T> is as follows

return _fixture.Create<T>();

Upvotes: 3

Views: 3683

Answers (4)

Jim Aho
Jim Aho

Reputation: 11887

Most creds to Mark Seeman above, but this is what worked for me (tweaked his code):

This change was important: request as ParameterInfo

public class RandomEnumSequenceBuilder<T> : ISpecimenBuilder where T : struct
{
    private readonly T[] _values;

    public RandomEnumSequenceBuilder()
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("T must be an enum");
        }
        _values = Enum.GetValues(typeof(T)).Cast<T>().ToArray();
    }

    public object Create(object request, ISpecimenContext context)
    {
        var t = request as ParameterInfo;
        if (t == null || t.ParameterType != typeof(T))
            return new NoSpecimen();

        var index = Random.Shared.Next(0, _values.Length - 1);
        return _values.GetValue(index)!;
    }
}

Upvotes: 0

arni
arni

Reputation: 2397

Revised the answer of @mark-seemann. Note that the upper bound of Random.Next is exclusive.

public class RandomEnumSequenceGenerator : ISpecimenBuilder
{
    private static readonly Random Random = new Random();

    public object Create(object request, ISpecimenContext context)
    {
        var seededRequest = request as SeededRequest;
        var type = seededRequest?.Request as Type;
        if (type == null || !type.IsEnum)
        {
            return new NoSpecimen();
        }

        var values = Enum.GetValues(type);
        var index = Random.Next(values.Length);
        return values.GetValue(index);
    }
}

Usage:

Fixture.Customizations.Add(new RandomEnumSequenceGenerator());

Upvotes: 4

Mark Seemann
Mark Seemann

Reputation: 233150

Your version of RandomEnumSequenceGenerator<T> doesn't check the request, so it just responds to any request, even if it isn't a request for the type it customises.

The easiest fix is probably something like this:

public class RandomEnumSequenceGenerator<T> : ISpecimenBuilder where T : struct
{
    private static Random _random = new Random();
    private Array _values;

    public RandomEnumSequenceGenerator()
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("T must be an enum");
        }
        _values = Enum.GetValues(typeof(T));
    }

    public object Create(object request, ISpecimenContext context)
    {
        var t = request as Type;
        if (t == null || t != typeof(T))
            return new NoSpecimen();

        var index = _random.Next(0, _values.Length - 1);
        return _values.GetValue(index);
    }
}

I haven't tried to compile and test this, so you may have to tweak it a bit, but it should demonstrate the gist of what you'll have to do.

Upvotes: 4

Xiaoy312
Xiaoy312

Reputation: 14477

This should fixes the type cast exception, assuming that is the culprit :

public class RandomEnumSequenceGenerator<T> : ISpecimenBuilder where T : struct
{
    private static Random _random = new Random();
    private T[] _values;

    public RandomEnumSequenceGenerator()
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("T must be an enum");
        }

        _values = Enum.GetValues(typeof(T))
            .Cast<T>()
            .ToArray();
    }

    public object Create(object request, ISpecimenContext context)
    {
        var index = _random.Next(_values.Length);
        return _values[index];
    }
}

Upvotes: 1

Related Questions