Unit Test In .NET Core Application Using XUnit

Introduction

There are three different test frameworks for Unit Testing supported by ASP.NET Core: MSTest, xUnit, and NUnit; which allow us to test our code in a consistent way. In this article, I will explain about the xUnit framework.

xUnit is an open source test framework and the main focus of this framework is on extensibility and flexibility. It follows more communities that are focusing on expanding their reach.

To demonstrate the example of a unit test, I have created an MVC project solution and a Unit test project by using CLI (Command Line Interface). To create an MVC and Test project, I am following the below steps.

Create a Solution file using the following command. This command creates an empty solution.

dotnet new sln -n MVCUnittest

Creating MVC Project

Using the following command, an MVC project will be created.

dotnet new MVC

Adding this project to the solution

Using the following command, we can add a project to the solution.

dotnet sln add Unittest\Unittest.csproj  

Create an XUnit test project

Using the following command, we can create an XUnit test project.

dotnet new xUnit  

This command creates an XUnit Test Project and the generated template configures the Test runner into a .csproj file.

<ItemGroup>
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
  <PackageReference Include="xunit" Version="2.3.1" />
  <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
  <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>

The generated code also has a dummy unit test file. It looks as follows.

using Xunit;
namespace TestProject
{
    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            // Write your test logic here
        }
    }
}

As compared to MsTest, XUnit has the Fact attribute that is applied to a method to indicate that it is a fact that should be run by the test runner.

Adding test project to the solution

>dotnet sln add TestProject\Testproject.csproj  

To demonstrate the concept, I have created a method within the HomeController class (GetEmployeeName). This method accepts empId as parameter and based on this, it will return the name of employee or "Not Found" hard code string.

HomeController

public string GetEmployeeName(int empId)
{
    string name;
    if (empId == 1)
    {
        name = "Jignesh";
    }
    else if (empId == 2)
    {
        name = "Rakesh";
    }
    else
    {
        name = "Not Found";
    }
    return name;
}

In the following test method, I have passed the hardcoded value and checked the result using the Assert class.

using UnitTest.Controllers;
using Xunit;
namespace TestProject
{
    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            HomeController home = new HomeController();
            string result = home.GetEmployeeName(1);
            Assert.Equal("Jignesh", result);
        }
    }
}

The final step is to run the Unit test. Using the following command, we can run all our test cases.

dotnet test
dotnet test --filter "FullyQualifiedName=TestProject.UnitTest1.Test1"

Result

Command

We also run all test cases or individual tests within Visual Studio using Test Explore.

Visual Studio

In the preceding example, my test result (actual) is matched with the expected result. In the following example, my actual result is not matching with the expected result.

[Fact]
public void Test2()
{
    HomeController home = new HomeController();
    string result = home.GetEmployeeName(1);
    Assert.Equal("Rakesh", result);
}

Result

Prompt

To unit test every block of code, we require more test data. We can add more test methods using the Fact attribute, but it is a very tedious job.

The XUnit supports other attributes also which enable us to write a suite for similar tests. A Theory attribute can be applied to the test that can take test data directly using the InlineData attribute or an Excel spreadsheet. Instead of creating a new test, we can use these two attributes: Theory and InlineData to create a single data-driven test.

using UnitTest.Controllers;
using Xunit;
namespace TestProject1
{
    public class UnitTest1
    {
        [Theory]
        [InlineData(1, "Jignesh")]
        [InlineData(2, "Rakesh")]
        [InlineData(3, "Not Found")]
        public void Test3(int empId, string name)
        {
            HomeController home = new HomeController();
            string result = home.GetEmployeeName(empId);
            Assert.Equal(name, result);
        }
    }
}

Result

Code

Unit test with ILogger

.NET Core supports built-in dependency injection. So, whatever services we want to use during the execution of the code, are injected as dependency. One of the best examples is the ILogger service. Using the following code, we can configure the ILogger service in our ASP.NET Core project.

Configure ILogger in the Program.cs

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
namespace Unittest
{
    public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }
        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureLogging((hostingContext, logging) =>
                {
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                    logging.AddConsole();
                    logging.AddDebug();
                })
                .UseStartup<Startup>()
                .Build();
    }
}

TestController.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Unittest.Controllers
{
    public class TestController : Controller
    {
        private readonly ILogger<TestController> _logger;
        public TestController(ILogger<TestController> logger)
        {
            _logger = logger;
        }
        public string GetMessage()
        {
            _logger.LogDebug("Test Method Called!!!");
            return "Hi! Reader";
        }
    }
}

Unit Test Method

To unit test the controller having a dependency on the ILogger service, we have to pass the ILogger object or null value to the constructor. To create these types of dependencies, we can create an object of service provider and with the help of the service provider, we can create the object of such services.

In the following code, I have created service provider object and created ILogger object.

[Fact]
public void Test4()
{
    var serviceProvider = new ServiceCollection()
        .AddLogging()
        .BuildServiceProvider();
    var factory = serviceProvider.GetService<ILoggerFactory>();
    var logger = factory.CreateLogger<TestController>();
    TestController home = new TestController(logger);
    string result = home.GetMessage();
    Assert.Equal("Hi! Reader", result);
}

Summary

Unit test is a code that helps us in verifying the expected behavior of the other code in isolation. Here, “In isolation" means there is no dependency between the tests. This is a better idea to test the application code before it goes for quality assurance (QA). All Unit test frameworks, MSTest, XUnit, and NUnit, offer a similar end goal and help us to write unit tests that are simpler, easier and faster.