You write Unit Tests wrong.

Serhii Korol - Nov 4 '23 - - Dev Community

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

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

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

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

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

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

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

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

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

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

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

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

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

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.

Buy Me A Beer

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .