In this article, we will explore how to automate test data generation, enhance productivity, improve test coverage, and reduce maintenance efforts with a single library called AutoFixture.
Let’s get started!
What Is AutoFixture?
AutoFixture is a powerful library for test data generation in .NET projects. Its main goal is to automate the process of creating test data by generating realistic values for our objects. It provides us with simple ways of obtaining both simple and complex data without having to define it by hand. AutoFixture also leverages reflection and customizable conventions to automatically create instances of classes with populated properties.
With AutoFixture we can remove the need for test maintenance as it automatically adapts to changes in our classes. Furthermore, it provides customization and extensibility options, allowing us to fashion its behavior to handle specific scenarios or implement specific rules during test data generation. It seamlessly integrates with popular .NET testing frameworks like NUnit and xUnit, easing its adoption and incorporation into existing testing setups.
Installing and Setting up AutoFixture
The first thing we have to do is install the NuGet package:
dotnet add package AutoFixture
Setting up the Code for Our Tests
Next, we will create the code to test out:
public class Employee { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public decimal Salary { get; set; } public Employee(string firstName, string lastName, int age, decimal salary) { FirstName = firstName; LastName = lastName; Age = age; Salary = salary; } public string GetFullName() => $"{FirstName} {LastName}"; }
Then, we move on to our department:
public class Department { public string Name { get; set; } public List<Employee> Employees { get; set; } public Department(string name) { Name = name; Employees = new List<Employee>(); } public void AddEmployee(Employee employee) { Employees.Add(employee); } public Employee? GetEmployee(string firstName) { return Employees .FirstOrDefault(e => e.FirstName == firstName); } public decimal CalculateAverageSalary() { if (!Employees.Any()) { return 0; } return Employees.Average(e => e.Salary); } }
Both of these classes are pretty simple to understand with straightforward logic.
Setting up AutoFixture for Test Data Generation
Let’s get AutoFixture up and running:
public class EmployeeTests { private readonly IFixture _fixture; public EmployeeTests() { _fixture = new Fixture(); } }
First, we create a xUnit test project. Then we create the EmployeeTests
class. It has a single private _fixture
field of IFixture
type, which is an interface provided by the AutoFixture library. In the constructor, we create a Fixture
instance and assigned it to the _fixture
field.
Those two lines are all we need as setup to start using AutoFixture.
Generating Basic Test Data With AutoFixture
We can use AutoFixture to easily generate simple reference and value types:
[Fact] public void WhenConstructorIsInvoked_ThenValidInstanceIsReturned() { // Arrange var firstName = _fixture.Create<string>(); var lastName = _fixture.Create<string>(); var age = _fixture.Create<int>(); var salary = _fixture.Create<decimal>(); // Act var employee = new Employee(firstName, lastName, age, salary); // Assert employee.FirstName.Should().Be(firstName); employee.LastName.Should().Be(lastName); employee.Age.Should().Be(age); employee.Salary.Should().Be(salary); }
First, we create a test method to verify the behavior of the constructor of the Employee
class. Then, we use the Create<T>()
method provided by AutoFixture to assign random values. We do that for firstName
, lastName
, age
, and salary
. The only difference is that for T
, we use the concrete type we need for each variable.
Next, we proceed by creating a new Employee
instance using the generated values. Finally, we assert that the properties of the created Employee
object match the respective input values. We make the assertions using the FluentAssertions library, which provides a readable syntax for expressing assertions.
We can use AutoFixture to get various basic and numeric types such as bool
, char
, string
, byte
, int
, double
, and decimal
.
But we can also use it to generate complex types as well:
[Fact] public void WhenAddEmployeeIsInvoked_ThenEmployeeIsAddedToTheList() { // Arrange var employee = _fixture.Create<Employee>(); var department = _fixture.Create<Department>(); var employeesCount = department.Employees.Count; // Act department.AddEmployee(employee); // Assert department.Employees.Count.Should().Be(employeesCount + 1); }
We create a unit test to verify the behavior of the Department
‘s class AddEmployee()
method. Then we use the Create<T>()
method to create instances of the Employee
and Department
classes. We also store the initial count of employees. After that, we invoke the AddEmployee
method on the department
, passing the employee
variable created by AutoFixture. Finally, we assert that the count of employees in the department has increased by one, ensuring that the employee was successfully added to the list.
Generating Custom Test Data With AutoFixture
One very useful side of AutoFixture is the ability to customize our data:
[Fact] public void WhenAddEmployeeIsInvoked_ThenEmployeeIsAddedToTheList() { // Arrange var employee = _fixture.Create<Employee>(); var employees = _fixture.CreateMany<Employee>(5).ToList(); var department = _fixture.Build<Department>() .With(x => x.Employees, employees) .Create(); // Act department.AddEmployee(employee); // Assert department.Employees.Count.Should().Be(6); }
We update our AddEmployee
method test by changing the logic for generating a Department
object. Then we use the Build<T>()
method of AutoFixture to start building an instance of our department.
We continue with the With
method to specify additional configurations for the object we are building. In this case, we focus on the Employees
property. The _fixture.CreateMany<Employee>(5)
call generates a collection of five instances of the Employee
class. As this returns an IEnumerable<T>
, we use the ToList()
method to convert the generated collection into a List<Employee>
.
Finally, we call the Create
method to finalize the creation of the Department
object with our custom preferences.
Using the Build<T>()
and With()
methods provided by Autofixture in combination with either Create<T>()
or CreateMany<T>()
we can achieve a high level of customization control over our test data. It’s good to remember that the Build<T>()
method only adds one-time customizations that are used for the creation of the next variable. Once we use Create()
or CreateMany()
, the customizations are lost.
Tips and Tricks for Generating Test Data With AutoFixture
This is by far not everything you could do with the library. Next, we’ll dive further into the various techniques and strategies that can help us enhance our test data generation process using AutoFixture.
Automatic Test Data Generation With AutoFixture
AutoFixture is so powerful that it can automatically generate values for us:
[Theory, AutoData] public void WhenConstructorIsInvoked_ThenValidInstanceIsReturned( string firstName, string lastName, int age, decimal salary) { // Act var employee = new Employee(firstName, lastName, age, salary); // Assert employee.FirstName.Should().Be(firstName); employee.LastName.Should().Be(lastName); employee.Age.Should().Be(age); employee.Salary.Should().Be(salary); }
We update the constructor test of the Employee
class by first changing the Fact
attribute to Theory
and adding another one called AutoData
. Then we add all the variables we need as method parameters. Once we do this, AutoFixture takes care of the rest and we can use the variables inside our method without having to declare and initialize them. By utilizing the AutoData
attribute we can further save time and effort when generating our test data.
Including or Omitting Property’s Initialization With AutoFixture
Using the library we can either include or commit certain properties from our test data, but need to make one change to our code first:
public Employee(string firstName, decimal salary) { FirstName = firstName; Salary = salary; }
We add a constructor overload to our Employee
class that only initializes FirstName
and Salary
. We do this because AutoFixture will try to create an instance of the requested object with the constructor having the least parameters.
Now that we have a constructor that doesn’t initialize all properties, let’s see how we can skip the optional ones:
[Fact] public void WhenAddEmployeeIsInvoked_ThenEmployeeIsAddedToTheList() { // Arrange var employee = _fixture.Build<Employee>() .OmitAutoProperties() .Create(); var employees = _fixture.Build<Employee>() .OmitAutoProperties() .CreateMany(5) .ToList(); var department = _fixture.Build<Department>() .With(x => x.Employees, employees) .Create(); // Act department.AddEmployee(employee); // Assert department.Employees.Count.Should().Be(6); }
In the AddEmployee
method tests, we don’t need the Employee
to have any properties other than the FirstName
and Salary
initialized. They are not vital for the behavior of the method, so they can be omitted. To achieve this we chain the Built<T>()
, where T
is Employee
, OmitAutoProperties()
, and CreateMany()
methods. The OmitAutoProperties()
method will prevent any properties not used in the constructor from being initialized.
We can also use OmitAutoProperties()
but still initialize some of the properties:
[Theory, AutoData] public void GivenEmployeeExists_WhenGetEmployeeIsInvoked_ThenEmployeeIsReturned( string firstName, string lastName) { // Arrange var employees = _fixture.Build<Employee>() .OmitAutoProperties() .With(x => x.FirstName, firstName) .With(x => x.LastName, lastName) .CreateMany(1) .ToList(); var department = _fixture.Build<Department>() .With(x => x.Employees, employees) .Create(); // Act var employee = department.GetEmployee(firstName); // Assert employee.Should().NotBeNull(); employee.LastName.Should().Be(lastName); employee.Age.Should().BeNull(); }
We create a test method to verify the behavior of the GetEmployee()
method. In the test method, we create an employees
variable and configure it using the Build<Employee>()
method. We also use the OmitAutoProperties()
method in combination to assign specific values for firstName
and lastName
.
Next, we create an Department
object and configure the Employees
property to return our already configured employees
variable. Then we invoke the GetEmployee()
method. Finally, we assert that the returned employee object is not null
, its LastName
matches the provided value, and its Age
property is null.
The alternative to OmitAutoProperties()
is the WithAutoProperties()
method:
[Fact] public void WhenCalculateAverageSalaryIsInvoked_ThenAccurateResultIsReturned() { // Arrange var employees = _fixture.Build<Employee>() .WithAutoProperties() .CreateMany(5) .ToList(); var department = _fixture.Build<Department>() .With(x => x.Employees, employees) .Create(); // Act var averageSalary = department.CalculateAverageSalary(); // Assert averageSalary.Should().BeGreaterThan(0); }
We test for the CalculateAverageSalary()
method, we use AutoFixture to create a Department
object. For its Employees
property, we use the employees
variable which we configure by using WithAutoProperties()
to state that we want all properties for each Employee
to be generated as well. Finally, we assert that the average salary is greater than 0.
AutoFixture Integration With Moq
Often, our classes have constructor parameters and depend on abstractions such as abstract classes and interfaces.
Let’s update our Department
class to illustrate this:
public abstract class Manager { public string? Name { get; set; } }
First, we define a Manager
class that is marked as abstract and has a Name
property of string
type.
Next, we create another abstraction:
public interface IPayrollService { void PaySalaries(IEnumerable<Employee> employee); }
We declare the IPayrollService
interface. It has a PaySalaries()
method that takes in an IEnumerable
of Employee
objects and is responsible for paying the salaries of those employees.
Finally, let’s update our Department
:
private readonly IPayrollService _payrollService; public string Name { get; set; } public Manager Manager { get; set; } public List<Employee> Employees { get; set; } public Department( string name, Manager manager, IPayrollService payrollService) { Name = name; Manager = manager; Employees = new List<Employee>(); _payrollService = payrollService; }
We create a field for the IPayrollService
interface and a property for the Manager
abstract class. For the last step, we update our constructor accordingly.
If we run our tests now, they will fail as AutoFixture can’t sort the dependencies on its own. To solve this, we can install and configure the AutoFixture.AutoMoq
package:
public DepartmentTests() { _fixture = new Fixture() .Customize(new AutoMoqCustomization() { ConfigureMembers = true }); }
In the DepartmentTests
constructor, we update the AutoFixture initialization by adding customization. We do that by using the Customize
method and passing a new instance of the AutoMoqCustomization
class. We also set ConfigureMembers
property to true
, which initializes any members of the abstract types it can, but this part is optional and we can skip it.
Finally, let’s update one of our test methods:
[Fact] public void WhenCalculateAverageSalaryIsInvoked_ThenAccurateResultIsReturned() { // Arrange var employees = _fixture.Build<Employee>() .WithAutoProperties() .CreateMany(5) .ToList(); var department = _fixture.Build<Department>() .With(x => x.Employees, employees) .Create(); // Act var averageSalary = department.CalculateAverageSalary(); // Assert averageSalary.Should().BeGreaterThan(0); department.Manager.Should().NotBeNull(); department.Manager.Name.Should().NotBeNull(); }
We update our CalculateAverageSalary
test method by adding two checks. They assert that both the Manager
and its Name
are not null. If we decide to skip ConfigureMembers
, the Manager
assertions will still pass but its Name
will be null
.
With this simple updated setup, we can make AutoFixture even more powerful and remove the need to explicitly mock and pass our abstract types in the constructor. This, like every other feature of the package, has the sole purpose of helping us write less code and make our tests shorter and more maintainable.
Conclusion
In this article, we explored how to automate test data generation and enhance productivity, improve test coverage, and reduce maintenance efforts with a single library called AutoFixture. By leveraging reflection and customizable conventions, AutoFixture simplifies test data generation. We also used the integration of AutoFixture with the Moq library, which allows us to handle constructor dependencies and abstractions. By leveraging its features, we can enhance productivity, improve test coverage, and reduce maintenance efforts.