Unit Testing in .NET Core with xUnit

Introduction

One of the main aspects of software testing that developers can manage themselves is unit testing. It is employed to test your code's tiniest elements. Unit tests are used to verify that your code is functioning as intended or producing the desired results as you develop it.

Testing the code before submitting it to source control is beneficial to all developers. There are numerous unit testing frameworks on the market, including NUnit, xUnit, and others.

In this article, we will investigate the process of unit testing.

Net Web API initiative. Since the xUnit framework is an open-source, free tool, we are using it here to accomplish the same. Net development. Here, we will begin by working on the most fundamental functions.

Web API

Creating a Testing Project

At last, we reach the stage where our tests will require the creation of a new project. We will take advantage of the handy xUnit testing project template that comes with Visual Studio 2022 when we use it.

An open-source unit testing tool for the.NET framework called xUnit makes testing easier and frees up more time to concentrate on creating tests:

Project

In order to inject the IEmployeeService interface into our controller during testing, we now need to construct our fictitious implementation of the interface.

We are going to populate its in-memory collection with our fictitious data.

public class EmployeeServiceFake : IEmployeeService
{
    public readonly Dictionary<Guid, Employee> _employees;

    public EmployeeServiceFake()
    {
        _employees = new Dictionary<Guid, Employee>
        {
            {
                new Guid("503df499-cabb-4699-8381-d76917365a9d"), 
                new Employee
                {
                    Id = new Guid("503df499-cabb-4699-8381-d76917365a9d"),
                    Name = "Name1",
                    Mno = "1234567890",
                    Salary = 36000,
                    Type = EmployeeType.Permanent
                }
            },
            {
                new Guid("77cf78a6-d6e8-4d50-afeb-39eae4567c62"), 
                new Employee
                {
                    Id = new Guid("77cf78a6-d6e8-4d50-afeb-39eae4567c62"),
                    Name = "Name2",
                    Mno = "1234567890",
                    Salary = 24000,
                    Type = EmployeeType.Temporary
                }
            }
        };
    }
    public bool Exists(Guid id)
    {
        return _employees.ContainsKey(id);
    }
    public Guid Create(Employee employee)
    {
        _employees.Add(employee.Id, employee);
        return employee.Id;
    }
    public void Delete(Guid id)
    {
        _employees.Remove(id);
    }
    public List<Employee> GetAll()
    {
        return _employees.Values.ToList();
    }
    public Employee GetById(Guid id)
    {
        if (!_employees.ContainsKey(id))
        {
            return null;
        }
        return _employees[id];
    }
    public Guid Update(Employee employee)
    {
        var emp = _employees[employee.Id];
        emp.Mno = employee.Mno;
        emp.Name = employee.Name;
        emp.Type = employee.Type;
        emp.Salary = employee.Salary;
        _employees[employee.Id] = emp;
        return employee.Id;
    }
}

Write some unit tests, please!

We are now prepared to write tests for our EmpController's first GetAll method.

The xUnit framework uses the [Fact] attribute, which we will use to decorate test methods to identify them as the real testing methods. In the test class, in addition to the test methods, we can have an infinite number of helper methods.

The AAA principle (Arrange, Act, and Assert) is typically followed when writing unit tests.

  • Arrange: this is the part where you usually get everything ready for the test or put another way, you get the scene ready for the test (making the objects and arranging them as needed).
  • Act: Here is where the procedure that we are testing is carried out.
  • Assert: In this last section of the test, we contrast the actual outcome of the test method's execution with our expectations.

Testing Our Actions

We will want to confirm the following in the Get method, which is the first method we are testing.

  • Whether the method yields the OkObjectResult, a response code of 200 for an HTTP request.
  • Whether the returned object includes every item in our list of Emps.

Testing the Get Method

public class EmployeeControllerTest
{
    private readonly EmployeeController _employeeController;
    private readonly IEmployeeService _employeeService;

    public EmployeeControllerTest()
    {
        _employeeService = new EmployeeServiceFake();
        _employeeController = new EmployeeController(_employeeService);
    }
    [Fact]
    public void Get_WhenCalled_ReturnsOkResult()
    {
        // Act
        var response = _employeeController.GetAll();
        // Assert
        Assert.IsType<OkObjectResult>(response as OkObjectResult);
    }
    [Fact]
    public void Get_WhenCalled_ReturnsAllItems()
    {
        // Act
        var response = _employeeController.GetAll() as OkObjectResult;
        // Assert
        var result = Assert.IsType<List<Employee>>(response.Value);
        Assert.Equal(2, result.Count);
    }
}

The class we wish to test is the one in which we create an instance of the EmployeeController object. It's crucial to remember that this constructor is called before every test method, which implies that the test is always run on a new object and the controller state is always reset.

This is crucial because, regardless of how often or in what order we run the tests, the test methods ought to be independent of one another and should yield consistent testing outcomes.

Testing the GetById Method

[Fact]
public void GetById_FakeGuidPassed_ReturnsNotFound()
{
    // Arrange
    var empId = Guid.NewGuid();
    // Act
    var response = _employeeController.GetById(empId);
    // Assert
    Assert.IsType<NotFoundResult>(response);
}
[Fact]
public void GetById_ValidGuidPassed_ReturnsOk()
{
    // Arrange
    var empId = new Guid("503df499-cabb-4699-8381-d76917365a9d");
    // Act
    var response = _employeeController.GetById(empId);
    // Assert
    Assert.IsType<OkObjectResult>(response);
}
[Fact]
public void GetById_ValidGuidPassed_ReturnsEmp()
{
    // Arrange
    var empId = new Guid("503df499-cabb-4699-8381-d76917365a9d");
    // Act
    var response = _employeeController.GetById(empId) as OkObjectResult;
    // Assert
    Assert.IsType<Employee>(response.Value);
    Assert.Equal(empId, (response.Value as Employee).Id);
}

Testing the Create Method

[Fact]
public void Create_Invalid_ReturnsBadRequest()
{
    // Arrange
    Employee? employee = null;
    // Act
    var response = _employeeController.Create(employee);
    // Assert
    Assert.IsType<BadRequestResult>(response);
}
[Fact]
public void Create_AlreadyExists_ReturnsBadRequest()
{
    // Arrange
    var employee = new Employee
    {
        Id = new Guid("503df499-cabb-4699-8381-d76917365a9d"),
        Name = "Name1",
        Mno = "1234567890",
        Salary = 36000,
        Type = EmployeeType.Permanent
    };
    // Act
    var response = _employeeController.Create(employee);
    // Assert
    Assert.IsType<BadRequestResult>(response);
}
[Fact]
public void Create_ValidEmployee_ReturnsOk()
{
    // Arrange
    var employee = new Employee
    {
        Id = Guid.NewGuid(),
        Name = "Name1",
        Mno = "1234567890",
        Salary = 36000,
        Type = EmployeeType.Permanent
    };
    // Act
    var response = _employeeController.Create(employee);
    // Assert
    Assert.IsType<CreatedAtActionResult>(response);
}
[Fact]
public void Create_ValidEmployee_ReturnsResponseItem()
{
    // Arrange
    var employee = new Employee
    {
        Id = Guid.NewGuid(),
        Name = "Name1",
        Mno = "1234567890",
        Salary = 36000,
        Type = EmployeeType.Permanent
    };
    // Act
    var response = _employeeController.Create(employee) as CreatedAtActionResult;
    var emp = response.Value as Employee;
    // Assert
    Assert.IsType<Employee>(emp);
    Assert.Equal(emp.Id, employee.Id);
}

Test Explorer

In brief

The test scenarios for our EmployeeController are now complete, and we would like to briefly review some general unit testing guidelines. When writing unit tests, there are a few standards or best practices you should aim for. It will undoubtedly make your life easier and the life of your fellow developers if you respect these practices.

  • Readability is a must for unit tests: Nobody wants to waste time attempting to decipher the purpose of your test. Ideally, it should be evident from the test name alone.
  • Maintainability is a must for unit tests: It is our goal to write our tests so that even small modifications to the code shouldn't require us to rewrite them all. We should treat our test code the same as the production code in this situation, adhering to the DRY (don't repeat yourself) principle. This lessens the likelihood that someone will eventually reach a point where it is too difficult for them to maintain all of our tests, and they must comment them out.
  • Unit sets ought to be quick: People are likely to run tests less frequently if they take too long to complete. That is undoubtedly undesirable, as nobody wants to have to wait around for tests to finish.
  • Dependencies shouldn't exist in unit tests: It is crucial that test execution be possible for everyone involved in the project without requiring access to an outside database or system. Testing must be done in complete isolation.
  • Make tests reliable instead of just focusing on code coverage: We should feel confident that we can find errors before they are introduced into the production system thanks to well-designed tests. It is simple to write tests that pass and have more code coverage by omitting certain assertions. However, there's no use in doing that. When it comes time to modify the code, we should make an effort to test the appropriate things so that we can rely on them.

In summary

This article has taught us the definition of unit testing and how to set up an xUnit project for unit testing.

The fundamental scenarios for testing the controller logic on a few CRUD operations have also been taught to us.

You should find these examples to be a great place to start when creating your own unit tests and testing larger, more intricate projects.

Thank you for reading, and hopefully, this post will help you understand ASP.NET Core unit testing and concepts a little bit better.

We learned the new technique and evolved together.

Happy coding!


Similar Articles