You write Unit Tests wrong.
Serhii Korol
Posted on November 4, 2023
Every one of us writes unit tests. Because it is a de facto standard of development. You tend to use a set of packages for testing, such as xUnit, FluentAssertation, Bogus, and Moq. In this article, I want to show you the Swiss knife of Unit testing. Today, we'll speak about AutoFixture. It's a very powerful tool for unit testing for everyday routines. And most mainly, it allows you to write less code in the Arrange block. Let's demonstrate it in practice. I created a base test project from a template where was already installed a bunch of packages needed for running unit tests with the xUnit library. To use AutoFixture, you need to install this package.
To start using it, you should create AutoFixture in your test class.
private readonly IFixture _fixture;
public SampleTests()
{
_fixture = new Fixture();
}
The AutoFixture uses the generic Create method. It creates random data, and you no longer need manual fill except for specific values. In the first example, I show how to make primitive data.
[Fact]
public void WhenEmployeeInstanceCreated_ThenCreatedValidInstance()
{
//Arrange
var name = _fixture.Create<string>();
var surname = _fixture.Create<string>();
var age = _fixture.Create<int>();
var position = _fixture.Create<string>();
var rate = _fixture.Create<decimal>();
//Act
var employee = new Employee(name, surname, age, position, rate);
//Assert
employee.Name.Should().Be(name);
employee.Surname.Should().Be(surname);
employee.Age.Should().Be(age);
employee.Position.Should().Be(position);
employee.Rate.Should().Be(rate);
}
However, this test can be improved. But you need to install an additional package, AutoFixture.Xunit2
. As you can see, the Arrange block was deleted.
[Theory, AutoData]
public void WhenEmployeeInstanceCreatedWithAutoData_ThenCreatedValidInstance(
string name, string surname, int age, string position, decimal rate)
{
// Act
var employee = new Employee(name, surname, age, position, rate);
// Assert
employee.Name.Should().Be(name);
employee.Surname.Should().Be(surname);
employee.Age.Should().Be(age);
employee.Position.Should().Be(position);
employee.Rate.Should().Be(rate);
}
The AutoFixture can work with classes and also generate data for each property.
[Fact]
public void WhenAddNewEmployee_ThenAddedOneEmployee()
{
// Arrange
var employee = _fixture.Create<Employee>();
var company = _fixture.Create<Company>();
var employeesCount = company.Employees.Count;
// Act
company.AddEmployee(employee);
// Assert
company.Employees.Count.Should().Be(employeesCount + 1);
}
Sure, you can manage it, and you can set specific data for specific properties. We also can create collections of items. Let's do it.
[Fact]
public void WhenRemoveEmployee_ThenRemovedEmployee()
{
// Arrange
var employees = _fixture.CreateMany<Employee>(5).ToList();
var company = _fixture.Build<Company>()
.With(x => x.Employees, employees)
.Create();
// Act
company.RemoveEmployee(employees.First());
// Assert
company.Employees.Count.Should().Be(4);
}
We also can ignore specific properties if needed.
[Fact]
public void WhenAddNewEmployee_CreatedInstanceWithAutoProperties()
{
// Arrange
var employee = _fixture.Build<Employee>()
.OmitAutoProperties()
.Create();
var employees = _fixture.Build<Employee>()
.OmitAutoProperties()
.CreateMany(5)
.ToList();
var company = _fixture.Build<Company>()
.With(x => x.Employees, employees)
.Without(x => x.City)
.Create();
// Act
company.AddEmployee(employee);
// Assert
company.Employees.Count.Should().Be(6);
company.City.Should().Be(null);
}
Let's consider another example that also allows ignore properties. If you set OmitAutoProperties
, it means that filled be only auto properties. The rest properties will be NULL.
[Theory, AutoData]
public void GetEmployee_WhenSpecificNameAndSurname_ThenEmployeeIsReturned(
string name, string surname)
{
// Arrange
var employees = _fixture.Build<Employee>()
.OmitAutoProperties()
.With(x => x.Name, name)
.With(x => x.Surname, surname)
.CreateMany(1)
.ToList();
var department = _fixture.Build<Company>()
.With(x => x.Employees, employees)
.Create();
// Act
var employee = department.GetEmployee(name);
// Assert
employee.Should().NotBeNull();
employee?.Name.Should().Be(name);
employee?.Email.Should().Be(null);
}
We considered too simple tests, but most tests have moq services. And AutoFixture also works with it. If you prefer the Moq package, then you need to install the package AutoFixture.AutoMoq
. The AutoFixture uses the Moq library that was integrated for work with AutoFixture. However, you need to add some configuration for using it. Modify AutoFixture initialization.
_fixture = new Fixture()
.Customize(new AutoMoqCustomization()
{
ConfigureMembers = true
});
The Moq uses as usual.
[Theory, AutoData]
public void CalculateSalary_ThenValidInstanceIsReturnedMoq(string name)
{
// Act
var payrollService = new Mock<IPayrollService>();
var employee = _fixture.Create<Employee>();
payrollService.Setup(x => x.GetSalary(employee)).Returns(1000);
var company = new Company(name, payrollService.Object);
var salary = company.GetSalary(employee);
// Assert
salary.Should().Be(1000);
}
If you are used to it, use NSubstitute. Sure, you also can add it. Just install the AutoFixture.AutoNSubstitute
package and add another configuration.
_fixture = new Fixture()
.Customize(new AutoMoqCustomization()
{
ConfigureMembers = true
}).Customize(new AutoNSubstituteCustomization()
{
ConfigureMembers = true
})
Using the NSubstitute is also as usual.
[Theory, AutoData]
public void CalculateSalary_ThenValidInstanceIsReturnedNSubstitute(string name)
{
// Arrange
var payrollService = Substitute.For<IPayrollService>();
var employee = _fixture.Create<Employee>();
payrollService.GetSalary(employee).Returns(1000);
// Act
var company = new Company(name, payrollService);
var salary = company.GetSalary(employee);
// Assert
salary.Should().Be(1000);
}
If you like to use FakeItEasy, then I also have good news for you. If you install the AutoFixture.AutoFakeItEasy
package and set the configuration, you can generate fake data.
_fixture = new Fixture()
.Customize(new AutoMoqCustomization()
{
ConfigureMembers = true
}).Customize(new AutoNSubstituteCustomization()
{
ConfigureMembers = true
}).Customize(new AutoFakeItEasyCustomization()
{
ConfigureMembers = true
});
If you are used to Bogus, unfortunately, the AutoFixture does not support it.
[Fact]
public void WhenEmployeeInstanceCreatedFake_ThenCreatedValidInstance()
{
//Arrange
var employee = A.Fake<Employee>();
var company = A.Fake<Company>();
//Act
company.AddEmployee(employee);
var currentEmployee = company.Employees.Single();
//Assert
currentEmployee.Name.Should().Be(employee.Name);
currentEmployee.Surname.Should().Be(employee.Surname);
currentEmployee.Age.Should().Be(employee.Age);
currentEmployee.Position.Should().Be(employee.Position);
currentEmployee.Rate.Should().Be(employee.Rate);
}
In conclusion, AutoFixture can help you reduce the code count and minimize hardcoded data. Also, AutoFixture significantly simplifies writing unit tests, making them more readable and clean.
I hope this information was helpful to you, and I wish you always green tests and, sure, happy coding.
The source code is here.
Posted on November 4, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.