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; that 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 MVC and Test project, I am following the below steps.

Create Solution file using the following command. This command creates an empty solution.
  1. >dotnet new sln -n MVCUnittest  
Creating MVC Project -

Using the following command, an MVC project will be created.
  1. >dotnet new MVC  
Adding this project to solution -

Using the following command, we can add project to the solution.
  1. >dotnet sln add Unittest\Unittest.csproj  
Create XUnit test project -

Using the following command, we can create XUnit test project.
  1. >dotnet new xUnit  
This command creates an XUnit Test Project and the generated template configures Test runner into .csproj file.
  1. <ItemGroup>  
  2.   <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />  
  3.   <PackageReference Include="xunit" Version="2.3.1" />  
  4.   <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />  
  5.   <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />  
  6. </ItemGroup>  
The generated code also has a dummy unit test file. It looks as follows.
  1. using Xunit;  
  2.   
  3. namespace TestProject  
  4. {  
  5.     public class UnitTest1  
  6.     {  
  7.         [Fact]  
  8.         public void Test1()  
  9.         {  
  10.               
  11.         }         
  12.     }  
  13. }  
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 solution
  1. >dotnet sln add TestProject\Testproject.csproj  
To demonstrate the concept, I have created a method within 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
  1. public string GetEmployeeName(int empId)  
  2. {  
  3.     string name;  
  4.     if (empId == 1)  
  5.     {  
  6.         name = "Jignesh";  
  7.     }  
  8.     else if (empId == 2)  
  9.     {  
  10.         name = "Rakesh";  
  11.     }  
  12.     else  
  13.     {  
  14.         name = "Not Found";  
  15.     }  
  16.     return name;  
  17. }  
In the following test method, I have passed hardcoded value and check result using Assert class.
  1. using UnitTest.Controllers;    
  2. using Xunit;    
  3.     
  4. namespace TestProject    
  5. {    
  6.     public class UnitTest1    
  7.     {    
  8.         [Fact]    
  9.         public void Test1()    
  10.         {    
  11.             HomeController home = new HomeController();    
  12.             string result = home.GetEmployeeName(1);    
  13.             Assert.Equal("Jignesh", result);    
  14.         }    
  15.     }    
  16. }    
The final step is to run the Unit test. Using the following command, we can run all our test cases.
  1. >dotnet test  
  2. >dotnet test --filter "FullyQualifiedName=TestProject.UnitTest1.Test1"  
Result


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


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.
  1. [Fact]  
  2. public void Test2()  
  3. {  
  4.     HomeController home = new HomeController();  
  5.     string result = home.GetEmployeeName(1);  
  6.     Assert.Equal("Rakesh", result);  
  7. }  
Result


To unit test every block of code, we require more test data. We can add more test methods using 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 InlineData attribute or an Excel spread sheet. Instead of creating a new test, we can use these two attributes: Theory and InlineData to create a single data driven test.
  1. using UnitTest.Controllers;  
  2. using Xunit;  
  3.   
  4. namespace TestProject1  
  5. {  
  6.     public class UnitTest1  
  7.     {  
  8.         [Theory]  
  9.         [InlineData(1, "Jignesh")]  
  10.         [InlineData(2, "Rakesh")]  
  11.         [InlineData(3, "Not Found")]  
  12.         public void Test3(int empId, string name)  
  13.         {  
  14.             HomeController home = new HomeController();  
  15.             string result = home.GetEmployeeName(empId);  
  16.             Assert.Equal(name, result);  
  17.         }  
  18.     }  
  19. }  
Result

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

Configure ILogger in Program.cs
  1. using Microsoft.AspNetCore;  
  2. using Microsoft.AspNetCore.Hosting;  
  3. using Microsoft.Extensions.Logging;  
  4.   
  5. namespace Unittest  
  6. {  
  7.     public class Program  
  8.     {  
  9.         public static void Main(string[] args)  
  10.         {  
  11.             BuildWebHost(args).Run();  
  12.         }  
  13.   
  14.         public static IWebHost BuildWebHost(string[] args) =>  
  15.             WebHost.CreateDefaultBuilder(args)  
  16.                 .ConfigureLogging((hostingContext, logging) =>  
  17.                 {  
  18.                     logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));  
  19.                     logging.AddConsole();  
  20.                     logging.AddDebug();  
  21.                 })  
  22.                 .UseStartup<Startup>()  
  23.                 .Build();  
  24.     }  
  25. }  
TestController.cs
  1. using Microsoft.AspNetCore.Mvc;  
  2. using Microsoft.Extensions.Logging;  
  3.   
  4. namespace Unittest.Controllers  
  5. {  
  6.     public class TestController : Controller  
  7.     {  
  8.         private readonly ILogger _logger;  
  9.         public TestController(ILogger<TestController> logger)  
  10.         {  
  11.             _logger = logger;  
  12.         }  
  13.           
  14.         public string GetMessage()  
  15.         {  
  16.            _logger.LogDebug("Test Method Called!!!");  
  17.             return "Hi! Reader";  
  18.         }  
  19.     }  
  20. }  
Unit Test Method
 
To unit test the controller having dependency on ILogger service, we have to pass 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.
  1. [Fact]  
  2. public void Test4()  
  3. {  
  4.     var serviceProvider = new ServiceCollection()  
  5.         .AddLogging()  
  6.         .BuildServiceProvider();  
  7.   
  8.     var factory = serviceProvider.GetService<ILoggerFactory>();  
  9.   
  10.     var logger = factory.CreateLogger<TestController>();  
  11.     TestController home = new TestController(logger);  
  12.     string result = home.GetMessage();  
  13.     Assert.Equal("Hi! Reader", result);  
  14. }  
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.