Pure.Krome
Pure.Krome

Reputation: 87047

Can AutoFixture generate random strings/text from a provided data set?

It is possible to use AutoFixture to generate random data for a string property .. but it's from a fixed data source?

For example: I have 30 street names hardcoded into a memory collection (array/list/whatever). Then, for my Address instance, the StreetName property isn't just a random string value (which is the default result from AutoFixture) but one of the street names from that hardcoded collection.

My first thought was to use a random number which AutoFixture might be able to create.. and this number is inside the array length/size ... so in effect i'm randomizing an array slot. Then, using this random number, get the value (aka street name) of the collection/array slot (ie. given the indexer, get the value at that index location).

Is this how it should be done?

Upvotes: 6

Views: 5711

Answers (1)

Mark Seemann
Mark Seemann

Reputation: 233367

As with so many other things regarding AutoFixture, things become much easier if you can use more explicit domain modelling. Instead of modelling StreetName as a string, introduce a domain object for it:

public sealed class StreetName
{
    private readonly string value;

    public StreetName(string streetName)
    {
        value = streetName ?? throw new ArgumentNullException(nameof(streetName));
    }

    public override bool Equals(object obj)
    {
        var other = obj as StreetName;
        if (other == null)
            return base.Equals(obj);

        return Equals(value, other.value);
    }

    public override int GetHashCode()
    {
        return value.GetHashCode();
    }

    public override string ToString()
    {
        return value;
    }

    public static implicit operator string(StreetName streetAddress)
    {
        return streetAddress.value;
    }

    public static implicit operator StreetName(string streetAddress)
    {
        return new StreetName(streetAddress);
    }
}

This is one of those modelling steps that are painful in C# and Java, but would be a one-liner in F# or Haskell...

Let's assume, however, that we have a list of predefined street names:

public static class StreetNames
{
    public static IEnumerable<string> Values = new[] {
        "221 B Baker St.",
        "1313 Webfoot Walk",
        "420 Paper St.",
        "42 Wallaby Way"
        /* More addresses go here... */ };
}

You can now trivially tell AutoFixture to pick only from that list, using ElementsBuilder:

var fixture = new Fixture();
fixture.Customizations.Add(
    new ElementsBuilder<StreetName>(StreetNames.Values.Select(s => (StreetName)s)));

At this point, though, it means that when you create StreetName values with AutoFixture, it'll pick from StreetNames.Values, but it still isn't going to do that when you ask it to create Address values. You can address (ha ha) that issue with a little ISpecimenBuilder:

public class StreetNameBuilder : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        var pi = request as PropertyInfo;
        if (pi == null || pi.Name != "StreetName" || pi.PropertyType != typeof(string))
            return new NoSpecimen();

        var sn = context.Resolve(typeof(StreetName));
        return (string)(StreetName)sn;
    }
}

Now you can configure your Fixture like this:

var fixture = new Fixture();
fixture.Customizations.Add(
    new ElementsBuilder<StreetName>(StreetNames.Values.Select(s => (StreetName)s)));
fixture.Customizations.Add(new StreetNameBuilder());

It'll now create Address values with StreetName values picked from the predefined list.

If you can't change your domain model, you can still add a class like StreetName. Just add it to your test code base instead of the production code base.

Upvotes: 5

Related Questions