Unit Testing in .NET Core - Getting Started with xUnit.net

This is the second post in our Unit Testing in .NET Core series! In the previous post, we covered an introduction to unit testing and explored the various testing frameworks available in .NET Core. Now, in this post, we'll take a deep dive into xUnit.net and learn how to write our first unit test using this powerful library. So let's get started.

Setting up the project

To begin our journey into writing our first unit test, we'll create a straightforward calculator application. Using Visual Studio, we'll start by setting up a class library project named SimpleCalculatorApp. Inside this project, we'll add a class called Calculator.cs, where we'll implement separate methods for Add, Subtract, Multiply, and Divide operations. Here's the resulting code section for the Calculator.cs class.

namespace SimpleCalculatorApp
{
    public class Calculator
    {
        public int Add(int num1, int num2)
        {
            return num1 + num2;
        }

        public int Subtract(int num1, int num2)
        {
            return num1 - num2;
        }

        public int Multiply(int num1, int num2)
        {
            return num1 * num2;
        }

        public int Divide(int num1, int num2)
        {
            if (num2 == 0)
            {
                throw new ArgumentException("Cannot divide by zero.");
            }

            return num1 / num2;
        }
    }
}

Setting up the test project

Now that we have our Calculator class and methods ready for testing, let's start writing the unit tests. To do this, we'll add a Unit Test project to our application by following these simple steps:

  1. In Visual Studio, right-click on the existing solution that contains the SimpleCalculatorApp project, and choose "Add" from the context menu.

  2. Select "New Project" to open the Create a new project dialog.

  3. In the search box, type "xUnit" to find the xUnit.net Test Project template.

  4. Choose the xUnit Test Project template, and give it a name like SimpleCalculatorApp.Tests, and click "Create."

Once the Unit Test project is added, we are ready to begin writing and running unit tests for the Calculator class. To do that, we need to add a reference to the SimpleCalculatorApp project in our test project. For that perform the following steps.

  1. Right-click on the SimpleCalculatorApp.Tests project in the Solution Explorer.

  2. Select "Edit Project File" from the context menu.

  3. Add a reference to the SimpleCalculatorApp project by adding the following lines inside the <ItemGroup> element:

     <ItemGroup>
         <ProjectReference Include="..\SimpleCalculatorApp\SimpleCalculatorApp.csproj" />
     </ItemGroup>
    

Create and Run xUnit Tests

  1. In the SimpleCalculatorApp.Tests project, we will find a default test class named UnitTest1.cs.

  2. Rename UnitTest1.cs to CalculatorTests.cs. A recommended practice is to name our test classes using the pattern <class-under-test>Tests.cs.

  3. Inside the CalculatorTests class, let's write our first xUnit test method. In this test method, we will test the Add method from the Calculator class.

     using SimpleCalculatorApp;
     using Xunit;
    
     public class CalculatorTests
     {
         [Fact]
         public void Add_ShouldReturnCorrectSum()
         {
             // Arrange
             var calculator = new Calculator();
             int a = 5;
             int b = 3;
    
             // Act
             int result = calculator.Add(a, b);
    
             // Assert
             Assert.Equal(8, result);
         }
     }
    

    Let's break down the above code step by step.

    1. The code starts by importing the Xunit namespace, which provides the necessary classes and attributes to write unit tests.

    2. The CalculatorTests class is created, which will contain our unit test methods. It is marked with the [Fact] attribute, indicating that the method inside this class is a test that should be executed. The [Fact] is an attribute provided by xUnit to define a method as a unit test method. The test runners will take up all the methods decorated by [Fact] attribute for execution.

    3. This is followed by the test method itself. The test method is called Add_ShouldReturnCorrectSum. This method tests the Add method of a class called Calculator. The test method is divided into three parts - Arrange, Act and Assert.

    4. The Arrange section is where we set up the test environment. We create an instance of the Calculator class and assign values to two variables, a and b, representing positive numbers (in this case, 5 and 3).

    5. The Act section is where we perform the action we want to test. Here, we call the Add method of the Calculator class and pass the values of a and b as arguments. The result of the addition is stored in a variable called result.

    6. The Assert section is where we verify whether the test passed or failed. We use the Assert.Equal method from Xunit to check if the result of the addition is equal to the expected value, which is 8 (5 + 3 = 8).

Running the Test

Now that we have written our first unit test, we need to run the tests. For running the test we need to open the Test Explorer in Visual Studio. For this, choose Test on the Visual Studio menu, choose Windows, and then choose Test Explorer (or press Ctrl + E, T). When the Test Explorer window is loaded we can see our Add_ShouldReturnCorrectSum test in the window.

Right-click on the test and choose the Run option. The test runner will execute the test and we can see the results in the test runner window. As our test is passed we can see a green checkmark next to the test. If it had failed we shall see a red X symbol next to the test and additional information about the failure.

Let's now explore how we can test exceptions using the Divide method as an example.

Unit Testing Exceptions

It's essential to ensure that our code handles exceptional scenarios gracefully, such as preventing division by zero when performing mathematical operations. Let's take a look at how we can test the Divide method to verify its exception-handling behavior.

To write a unit test for the exception scenario, we'll use xUnit.net's Assert.Throws method. Here's the test code:

[Fact]
public void Divide_ShouldThrowArgumentException_WhenDividingByZero()
{
    // Arrange
    Calculator calculator = new Calculator();
    int numerator = 10;
    int denominator = 0;

    // Act and Assert
    Assert.Throws<ArgumentException>(() => calculator.Divide(numerator, denominator));
}

In the test method Divide_ShouldThrowArgumentException_WhenDividingByZero, we first arrange the necessary setup by creating an instance of the Calculator class. Next, we set up the input values, where numerator can be any valid number, but denominator is intentionally set to zero to invoke the exception scenario.

The core of this test is the line Assert.Throws<ArgumentException>(() => calculator.Divide(numerator, denominator));. This line instructs xUnit to execute the Divide method with the provided arguments and verify that it indeed throws an ArgumentException. If the exception is thrown as expected, the test will pass successfully, indicating that our code correctly handles the exceptional scenario of division by zero.

If we run the test we can see that the test indeed is passing.

Add the remaining tests

To ensure our code works correctly, we'll add unit tests for the remaining methods: Subtract, Multiply, and the non-zero denominator scenario of Divide. Once these tests are added, our test class will be complete.

using System;
using SimpleCalculatorApp;
using Xunit;

namespace SimpleCalculatorApp.Tests
{
    public class CalculatorTests
    {
        [Fact]
        public void Add_ShouldReturnCorrectSum()
        {
            // Arrange
            Calculator calculator = new Calculator();
            int num1 = 5;
            int num2 = 10;

            // Act
            int result = calculator.Add(num1, num2);

            // Assert
            Assert.Equal(15, result);
        }

        [Fact]
        public void Subtract_ShouldReturnCorrectDifference()
        {
            // Arrange
            Calculator calculator = new Calculator();
            int num1 = 15;
            int num2 = 7;

            // Act
            int result = calculator.Subtract(num1, num2);

            // Assert
            Assert.Equal(8, result);
        }

        [Fact]
        public void Multiply_ShouldReturnCorrectProduct()
        {
            // Arrange
            Calculator calculator = new Calculator();
            int num1 = 3;
            int num2 = 6;

            // Act
            int result = calculator.Multiply(num1, num2);

            // Assert
            Assert.Equal(18, result);
        }

        [Fact]
        public void Divide_ShouldReturnCorrectQuotient()
        {
            // Arrange
            Calculator calculator = new Calculator();
            int num1 = 20;
            int num2 = 4;

            // Act
            int result = calculator.Divide(num1, num2);

            // Assert
            Assert.Equal(5, result);
        }

        [Fact]
        public void Divide_ShouldThrowArgumentException_WhenDividingByZero()
        {
            // Arrange
            Calculator calculator = new Calculator();
            int num1 = 10;
            int num2 = 0;

            // Act & Assert
            Assert.Throws<ArgumentException>(() => calculator.Divide(num1, num2));
        }
    }
}

Run all the tests. We can see that all the tests are indeed passing.

Summary

In this blog post, we delved into the world of setting up and writing unit tests using xUnit.net. Taking a simple calculator application as an example, we crafted unit tests for various methods. The journey included understanding the significance of the [Fact] attribute, mastering the Arrange-Act-Assert pattern, and effectively handling exception scenarios through unit testing.

Excitingly, there's more to explore in xUnit.net! In the upcoming posts, we'll explore additional features in xUnit.net that can enhance the maintainability and robustness of our tests.

0
Subscribe to my newsletter

Read articles from Geo J Thachankary directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Geo J Thachankary
Geo J Thachankary