I will try to keep this one short by not going into details of what Autofixture is , why we need it... I am assuming you would be familiar with it
What is a specimen ?
In programming we work with types(i.e. int
, string
, decimal
, etc. ) - a specimen is an example value of a particular type.
E.g. "Kanelbulle" is a specimen (an example value) for the type string or 0.23m , 0.50m are specimens for the type decimal.
What is a specimen builder in context Auto fixture ?
Having known what a specimen is now, we can safely assume that the logic ( basically set of instructions) to build a specific specimen is a specimen builder.
In terms of AutoFixture, it is a concrete implementation of the ISpecimenBuilder
Given a complex type like this
class Employee
{
public string Name { get; set; }
public int Code{ get; set; }
}
when we ask the fixture (fixture.Create<Employee>()
) to give us anonymous data of type Employee, behind the scenes a set of specimen builders (either default or custom) are going to run and generate us a specimen for a string and int (which are basically the two properties of the Employee class here).
For e.g. the default specimen builder for a string spits out a guid as the value for us. For a complex type , like Employee in this case, a string gets a specimen prepended with the property name (as it is the case for all the properties of a complex type) something like this Namead6fb2f0-d23a-4bb0-a083-8ffca9a139aa
Before moving to writing a custom (concrete) implementation of the ISpecimenBuilder
, I want to put across the way I have understood the AutoFixture pipeline (feel free to correct me here)
- AutoFixture is responsible for creating Anonymous data for a particular type of
T
(** anonymous data is anything which is not going to affect our SUT /system under test in anyway i.e. we don't care for the values but the instance or type is needed by our SUT regardless of its value) - T could be a primitive type or any custom defined type
- When creating an instance (anonymous data) of
T
, the first step is the customization phase where the AutoFixture library looks for a collection of the ISpecimenBuilders - AutoFixture has its own set of default Specimen Builders and then if we write any custom ones,they will override those default specimen builders if registered in this phase
5.The customization phase is taken care by calling the
Customizations
or theCustomize
on thefixture
(we will see in a bit) instance - The next step in the pipeline is then to "run" these concrete specimen builders to generate the specimen (example values) for a given type
- Last is the fall back step, where the Autofixture library applies fallbacks/defaults (tries real hard to generate a specimen for us !) and if still no specimen was able to be generated for a particular type(could be because of some validations in the constructor in case of a complex type), then eventually throw an error .
In nut shell , a request for fixture.Create() would go through a series of specimen builders to finally give us an instance of T
When would you want to create a custom specimen builder ?
If you want to influence the logic of building one in any specific way !
For e.g.
var fixture = new Fixture();
var date1 = fixture.Create<DateTime>();
The default specimen builder for DateTime
shipped with Autofixture is going to return you a specimen (value) of a random date time value per your system's culture but say you want to override this and instead want the UTC date to be returned as a specimen
In this case you can have your own specimen builder implementation. Refer this and you can build your UTC one in a similar way
public class CurrentUtcDateTimeGenerator : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
if (!typeof(DateTime).Equals(request))
{
return new NoSpecimen();
}
return DateTime.UtcNow;
}
}
and then register this specimen builder (remember the Customize stage which we spoke about earlier ?)
fixture.Customizations.Add(new CurrentUtcDateTimeGenerator());
next whenever we ask for an instance of type DateTime from this fixture it is going to give us the current UTC date
A lot more things can be achieved through writing your own specimen builders.
For e.g. say you have a class
public class Employee
{
public string Name { get; set; }
public string EmployeeCode { get; set; }
}
You want to influence how EmployeeCode is generated , then you could override the default guid specimen for a string here.
public class EmployeeCodeGenerator : ISpecimenBuilder
{
private readonly char[] _allowedChars = { 'V', 'I', 'D', 'S', 'H', 'A' };
public object Create(object request, ISpecimenContext context)
{
//we use reflection here and pattern matching to see if the specimen we are trying to build is a property
//we are interested in the EmployeeCode property of the Employee class
if (request is PropertyInfo propertyInfo)
{
//next we match the name to influence the specimen building logic for the EmployeeCode property
if (propertyInfo.Name.Contains("employeecode", StringComparison.InvariantCultureIgnoreCase))
{
//when we get hold of it we return our custom 3 digit code instead
return new string(_allowedChars, 0, 3);
}
}
return new NoSpecimen();
}
}
and then use the above like this
var fixture = new Fixture();
fixture.Customizations.Add(new EmployeeCodeGenerator());
var employee = fixture.Create<Employee>();
Assert.Equal(3, employee.EmployeeCode.Length);
Hope this was useful
Reference
Top comments (0)