Reputation: 1804
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
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
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
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
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