DEV Community

Vidisha Parab
Vidisha Parab

Posted on • Edited on

AutoFixture - Specimen Builders

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; }
}

Enter fullscreen mode Exit fullscreen mode

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)

  1. 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)
  2. T could be a primitive type or any custom defined type
  3. 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
  4. 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 the Customize on the fixture (we will see in a bit) instance
  5. The next step in the pipeline is then to "run" these concrete specimen builders to generate the specimen (example values) for a given type
  6. 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>();
Enter fullscreen mode Exit fullscreen mode

The default specimen builder for DateTimeshipped 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;
        }
    }
Enter fullscreen mode Exit fullscreen mode

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();
  }
 }

Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Hope this was useful

Reference

Convention-based Customizations with AutoFixture

Top comments (0)