AutoFixture and Custom DataAnnotations
Asked Answered
P

2

6

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?

Purplish answered 5/8, 2014 at 20:26 Comment(0)
S
4

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.

Soosoochow answered 5/8, 2014 at 22:24 Comment(1)
Thanks! That gives me a good idea of what I need to do. Wishing there were any easier way though, with 5 custom attributes that's 15 classes I need to make, not counting the customizations.Purplish
P
4

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!

Purplish answered 28/5, 2015 at 18:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.