TortillaCurtain
TortillaCurtain

Reputation: 531

How do I use the RandomRangedNumberCustomization in Autofixture to ensure parameters are in a certain range?

I have a class that takes two parameters as shown:

    public double X { get; private set; }

    public double Y { get; private set; }

    public Point(double x, double y)
    {
        if (x > 90 || x < -90)
            throw new ArgumentOutOfRangeException("latitude");

        if (y > 180 || y < -180)
            throw new ArgumentOutOfRangeException("longitude");

        X = x;
        Y = y;
    }

The corresponding properties are set in the constructor, so I need to tell AutoFixture to create a Point class with parameters in the range as specified in the guard clauses. I have managed to get a bit confused about the usage of the RandomRangedNumberCustomization class. I did the following:

        var xRange = new RangedNumberRequest(typeof(double), -90.0, 90.0);
        var yRange = new RangedNumberRequest(typeof (double), -180.0, 180.0);
        var dummyContext = new DelegatingSpecimenContext();
        var generator = new RandomRangedNumberGenerator();
        var x = (double)generator.Create(latitudeRange, dummyContext);
        var y = (double) generator.Create(longitudeRange, dummyContext);

which will generate numbers in my range, so I could just create a Point and feed in those generated numbers, but I'm missing something in terms of the customization. Any help and or guidance would be much appreciated.

Thanks!

Upvotes: 4

Views: 1481

Answers (2)

Mark Seemann
Mark Seemann

Reputation: 233397

Apart from the solutions suggested by Nikos Baxevanis, here's another option. You can implement a convention that always handles the x and y arguments for Point in a particular way:

public class PointCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(new XBuilder());
        fixture.Customizations.Add(new YBuilder());
    }

    private class XBuilder : ISpecimenBuilder
    {
        public object Create(object request, ISpecimenContext context)
        {
            var pi = request as ParameterInfo;
            if (pi == null ||
                pi.Name != "x" ||
                pi.Member.DeclaringType != typeof(Point))
                return new NoSpecimen(request);

            return context.Resolve(
                new RangedNumberRequest(typeof(double), -90d, 90d));
        }
    }

    private class YBuilder : ISpecimenBuilder
    {
        public object Create(object request, ISpecimenContext context)
        {
            var pi = request as ParameterInfo;
            if (pi == null ||
                pi.Name != "y" ||
                pi.Member.DeclaringType != typeof(Point))
                return new NoSpecimen(request);

            return context.Resolve(
                new RangedNumberRequest(typeof(double), -180d, 180d));
        }
    }
}

(There's room for refactoring in the above code.)

Given PointCustomization, this test passes:

[Fact]
public void CreatePointDoesNotThrow()
{
    var fixture = new Fixture().Customize(new PointCustomization());
    var e = Record.Exception(() => fixture.Create<Point>());
    Assert.Null(e);
}

Upvotes: 4

Nikos Baxevanis
Nikos Baxevanis

Reputation: 11211

There should be a couple of ways of customizing AutoFixture for this. – Here are a few of them:

With Data Annotations

public class Point
{
    public double X { get; private set; }
    public double Y { get; private set; }

    public Point(
        [Range( -90,  90)]double x,
        [Range(-180, 180)]double y)
    {
        if (x > 90 || x < -90)
            throw new ArgumentOutOfRangeException("latitude");

        if (y > 180 || y < -180)
            throw new ArgumentOutOfRangeException("longitude");

        this.X = x;
        this.Y = y;
    }
}

[Fact]
public void CreatePointDoesNotThrow()
{
    var fixture = new Fixture();
    Assert.DoesNotThrow(() => fixture.Create<Point>()); // Passes.
}

With Generators

public class Point
{
    public double X { get; private set; }
    public double Y { get; private set; }

    public Point(double x, double y)
    {
        if (x > 90 || x < -90)
            throw new ArgumentOutOfRangeException("latitude");

        if (y > 180 || y < -180)
            throw new ArgumentOutOfRangeException("longitude");

        this.X = x;
        this.Y = y;
    }
}

[Fact]
public void CreatePointDoesNotThrow()
{
    var fixture = new Fixture();
    var x = new Generator<int>(fixture).First(pt => pt >  -90 && pt <  90);
    var y = new Generator<int>(fixture).First(pt => pt > -180 && pt < 180);
    fixture.Customize<Point>(c => c
        .FromFactory(() => new Point(x, y)));

    Assert.DoesNotThrow(() => fixture.Create<Point>()); // Passes.
}

Upvotes: 4

Related Questions