Search code examples
unit-testingautofixture

How to fix a range on some properties when create a TestClass by AutoFixture


How can I tell AutoFixture to specify a range (min and max) on some properties when doing

MyDataClass obj = fixture.Create<MyDataClass>();

where MyDataClass has property Diameter and I only want min:1 and max:60 on this property?


Solution

  • Data Annotations

    The easiest approach is probably adorning the property itself with a Data Annotation, although I'm not myself a huge fan of this:

    public class MyDataClass
    {
        [Range(1, 60)]
        public decimal Diameter { get; set; }
    }
    

    AutoFixture will respect the [Range] attribute's values.

    Convention-based

    A better approach is, in my opinion, a convention-based approach that doesn't rely on non-enforceable attributes:

    public class DiameterBuilder : ISpecimenBuilder
    {
        public object Create(object request, ISpecimenContext context)
        {
            var pi = request as PropertyInfo;
            if (pi == null ||
                pi.Name != "Diameter" ||
                pi.PropertyType != typeof(decimal))
                return new NoSpecimen(request);
    
            return context.Resolve(
                new RangedNumberRequest(typeof(decimal), 1.0m, 60.0m));
        }
    }
    

    This passing test demonstrates how to use it:

    [Fact]
    public void ResolveRangeLimitedType()
    {
        var fixture = new Fixture();
        fixture.Customizations.Add(new DiameterBuilder());
        var actual = fixture.Create<Generator<MyDataClass>>().Take(100);
        Assert.True(actual.All(x => 1 <= x.Diameter && x.Diameter <= 60));
    }
    

    For more details, please refer to this other, very closely related SO Q&A.

    Overcoming Primitive Obsession

    Perhaps an even better approach is to listen to your tests, combat Primitive Obsession, and introduce a custom type - in this case, a Diameter Value Object.

    This is often my preferred approach.