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.