I'm using AutoFixture in my unit and integration tests and ran into a problem. I'm generating data transfer objects and some of those classes have DataAnnotation attributes (some of which are custom) on the properties. AutoFixture sees those and doesn't generate data for them, presumably because it is unsure what data it is expecting.
An example of a custom validation attribute I'm using:
public class Person
{
public string FullName { get; set; }
[Enum("M", "F")]
public string Gender { get; set; }
}
public class EnumAttribute : ValidationAttribute
{
public EnumAttribute(params string[] allowedStrings)
{
if (allowedStrings == null || allowedStrings.Length == 0)
throw new ArgumentException("allowedStrings");
AllowNull = true;
AllowedStrings = allowedStrings;
ErrorMessage = "The field '{0}' is invalid. Allowed strings are " + string.Join(", ", AllowedStrings);
}
// ... implementation
}
It just restricts a provided string to certain value (I couldn't use a straight up enum for other reasons).
How could I customize AutoFixture to create the appropriate data?
Currently, AutoFixture supports the following validation attributes:
To support a custom validation attribute you'll have to extrapolate from one of the following groups:
And then, you'll have to add the newly created Relay and Generator classes as Customizations
.
I didn't want to bother creating a Relay, Request and Generator for each custom attribute I had in my solution. Instead, I create an ISpecimenBuilder
that handled everything in one place.
In my case, I only have custom attributes on strings. Some custom attributes I created that I was looking for were AlphaNumericAttribute
, and EnumAttribute
.
So something like this:
public class CustomAnnotationsBuilder : ISpecimenBuilder
{
private readonly Random rnd = new Random();
public object Create(object request, ISpecimenContext context)
{
object result = new NoSpecimen(request);
var pi = request as PropertyInfo;
// do a few different inspections if it's a string
if (pi != null && pi.PropertyType == typeof(string))
{
// handle Enum attributes
if (Attribute.IsDefined(pi, typeof(EnumAttribute)))
{
var enumAttribute = (EnumAttribute)Attribute.GetCustomAttribute(
pi, typeof(EnumAttribute));
var allowedStrings = enumAttribute.GetAllowedStrings();
return allowedStrings[rnd.Next(0, allowedStrings.Length)];
}
if (Attribute.IsDefined(pi, typeof(StringLengthAttribute)))
{
var stringLengthAttribute = (StringLengthAttribute)Attribute.GetCustomAttribute(
pi, typeof(StringLengthAttribute));
minLength = stringLengthAttribute.MinimumLength;
maxLength = stringLengthAttribute.MaximumLength;
// do custom string generation here
return generatedString;
}
if (Attribute.IsDefined(pi, typeof(AlphaNumericAttribute)))
{
// do custom string generation here
return generatedString;
}
return result;
}
return result;
}
Then I could add that to AutoFixture like so:
Fixture.Customizations.Add(new CustomAnnotationsBuilder());
Fixture.Customize(new NoDataAnnotationsCustomization()); // ignore data annotations since I'm handling them myself
And that was it!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With