Anonymous Values and Equivalence Classes
Anonymous value support
The use of the anonymous variable pattern is a good pattern to use when you want to use values in your tests whose exact value isn't significant. By including a specific value you are making it look like that value is important in some way, which steals cognitive load from the test reader while they need to figure out that the value doesn't actually matter.
This is relevant when defining a test data builder because of the initial values that you set the different parameters to by default. For instance, if you had code like this:
class CustomerBuilder : TestDataBuilder<Customer, CustomerBuilder>
{
public CustomerBuilder()
{
WithFirstName("Rob");
WithLastName("Moore");
WhoJoinedIn(2013);
}
public CustomerBuilder WithFirstName(string firstName)
{
return Set(x => x.FirstName, firstName);
}
...
}
In that case the values "Rob", "Moore" and 2013 look significant on initial inspection. In reality it shouldn't matter what they are; any test where those values matter should specify them to make the intent clear.
When you use the Get method in a builder and you haven't called Set it will automatically generate an anonymous value for each requested value. This not only allows you to get rid of those insignificant values, but it allows you to trim down the constructor of your builder - making the builders terser and quicker to write.
Given we aren't talking about variables but rather values we have thus named the pattern anonymous values rather than anonymous variables.
There are a number of default conventions that are followed to determine what value to use via the Anonymous Value Fixture class. This works through the application of anonymous value suppliers - which are processed in order to determine if a value can be provided and if so a value is retrieved. At the time of writing the default suppliers are the following (applied in this order):
DefaultEmailValueSupplier
- Supplies an email address for all string properties with a property name containing 'email'DefaultFirstNameValueSupplier
- Supplies a first name for all string properties with a property name containing 'firstname' (case insensitive)DefaultLastNameValueSupplier
- Supplies a last name for all string properties with a property name containing 'lastname' or 'surname' (case insensitive)DefaultStringValueSupplier
- Supplies the property name followed by a random GUID for all string propertiesDefaultValueTypeValueSupplier
- Supplies an AutoFixture generated value for any value types (e.g. int, double, etc.)DefaultValueSupplier
- Supplies default(T)
This gets you started for the most basic of cases, but from there you have a lot of flexibility to apply your own suppliers on both a global basis (via AnonymousValueFixture.GlobalValueSuppliers
) and a local basis for each fixture instance (via fixture.LocalValueSuppliers
) - you just need to implement IAnonymousValueSupplier
. See the tests for examples.
Standalone value generation
While AnonymousValueFixture
is integrated in to the test data builder infrastructure, it's perfectly acceptable (and encouraged!) to use it on it's own as a powerful way to generate anonymous values for your tests. See below the canonical example of a calculator add method test. Note: this test is also a good example of the Derived Values pattern.
public class CalculatorShould
{
private AnonymousValueFixture Any = new AnonymousValueFixture();
private _calculator = new Calculator();
public void AddTwoPositiveNumbersCorrectly()
{
var x = Any.PositiveInteger();
var y = Any.PositiveInteger();
var expectedResult = x + y;
var result = _calculator.Add(x, y);
result.ShouldBe(expectedResult);
}
}
Equivalence classes support
The principle of constrained non-determinism frees you from having to worry about the fact that anonymous values can be random as long as they fall within the equivalence class of the value that is required for your test.
We think the same concept can and should be applied to test data builders. More than that, we think it enhances the ability for the test data builders to act as documentation. Having a constructor that reads like this for instance tells you something interesting about the Year property:
class CustomerBuilder : TestDataBuilder<Customer, CustomerBuilder>
{
public CustomerBuilder()
{
WhoJoinedIn(Any.YearAfter2001());
}
...
}
You may well use value objects that protect and describe the integrity of the data (which is great), but you can still create an equivalence class for the creation of the value object so I still think it's relevant beyond primitives.
We have some built-in equivalence classes that you can use to get started quickly for common scenarios. At the time of writing the following are available (as extension methods of the AnonymousValueFixture
class that is defined in a property called Any
on the test data builder base class):
Any.String()
Any.StringMatching(string regexPattern)
Any.StringStartingWith(string prefix)
Any.StringEndingWith(string suffix)
Any.StringOfLength(int length)
Any.PositiveInteger()
Any.NegativeInteger()
Any.IntegerExcept(int[] exceptFor)
Any.Of<TEnum>()
Any.Except<TEnum>(TEnum[] except)
Any.EmailAddress()
Any.UniqueEmailAddress()
Any.Language()
Any.FemaleFirstName()
Any.MaleFirstName()
Any.FirstName()
Any.LastName()
Any.Suffix()
Any.Title()
Any.Continent()
Any.Country()
Any.CountryCode()
Any.Latitude()
Any.Longitude()
There is nothing stopping you using the anonymous value fixture outside of the test data builders - you can create a property called Any
that is an instance of the AnonymousValueFixture
class in any test class.
Also, you can easily create your own extension methods for the values and data that makes sense for your application. See the source code for examples to copy.
A couple of things to note:
- You have the ability to stash information in the fixture by using the dynamic
Bag
property - You also have an
AutoFixture
instance available to use via theFixture
property
Side note: We feel that Dossier does some things that are not easy to do in AutoFixture, hence why we don't "just use AutoFixture" - we see Dossier as complimentary to AutoFixture because they are trying to achieve different (albeit related) things.
We got the idea for the Any.Whatever()
syntax from the TDD Toolkit by Grzegorz Gałęzowski. We really like it and we also highly recommend his TDD e-book.
Updated less than a minute ago