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

1

6

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?

Heliozoan answered 8/2, 2016 at 2:48 Comment(1)
Yes, this is possible: blog.ploeh.dk/2010/11/22/…Chipmunk
C
5

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.

Chipmunk answered 21/2, 2018 at 10:10 Comment(4)
Why can't you just add a function called One.Of(new {"Buy","Sell"}) or One.Of(params object[] string so that it's just One.Of("Buy","Sell") or One.Of(Enumerable.Range(1, 50)) ? I think your answer is overly complicated for what the user is asking, and the user is giving you good ideas on how to make AutoFixture easier to use. Usually, we want collections built off the following one or more combos: at most one element is specific value, all elements are unique, all elements are sub-sets of some other set of elements, all elements are unique sub-sets of some other set of elements, etc...Artefact
I see you wrote this answer 10 months before: twitter.com/AutoFixture/status/1072235866205118464 - but it seems like the remaining request is to allow One.Of to return an ElementBuilder<T> constructured with a value generation expression, similar to what GenFu/ObjectHydrator offers?Artefact
@JohnZabroski I handed over AutoFixture years ago. Please direct comments and questions to the current maintainers.Chipmunk
Thanks for the prompt reply. Done. github.com/AutoFixture/AutoFixture/issues/1179Artefact

© 2022 - 2024 — McMap. All rights reserved.