Unit testing with xUnit in Asp

Unit Testing With Xunit in asp.net Core Explained

What is Unit Testing?

Unit testing is a part of testing an app from its infrastructure. Unit testing tests the logic written in any specific action, not the behavior of dependencies.
As in unit testing, focus only on the controller action. Unit test controller avoids things like filters, routing, or model binding (the mapping of request data to a ViewModel or DTO). Because they focus on testing just one thing, unit tests are generally simple to write and quick to run.

Types of ways to do unit testing

We can do the unit test in the following ways.
xUnit
NUnit
MSTest

What is xUnit?

xUnit is a free, open-source, testing tool for .NET developers to use to write tests for their applications. It is essentially a testing framework that provides a set of attributes and methods we can use to write the test code for our applications. The following attributes are used while we use xUnit.
Fact – This attribute is used for executing the method by the test runner.
Theory – This attribute is used to send some parameters to our testing code. So, it is similar to the Fact attribute but additionally, the Theory attribute used to send parameters to the test method.
InlineData – This attribute provides those parameters we are sending to the test method. If we are using the Theory attribute, we have to use the InlineData as well

What Is NUnit?

The NUnit framework isolates .NET applications into modules for unit testing. In this, every module is tested separately. The NUnit Framework caters to a range of attributes that are used during unit tests. It defines Test-Fixtures, Test Methods, ExpectedExceptions, and Ignoremethods.

What is MSTest?

MSTest is one type of framework in which we can test the code without using any third-party tool. MSTest is used to run software tests. The MSTest framework helps in writing effective unit tests.

hire .net developers

What is a Fixture?

Fixture means some fixed environment in which test cases run with that fixed data.
It’s also known as test context.
Example:- Loading database with some fixed set of data.
A fixture is mostly used when we need to display some data using test cases on that time we can use the fixture class.
Example of Fixture class:-
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace API.Test
{
public class DatabaseFixture : IDisposable
{
public Context context { get; private set; }
public DatabaseFixture()
{
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development");

var builder = new DbContextOptionsBuilder<Context>()
.UseSqlServer(@"Test database Connection string"); 


context = new Context(builder.Options);
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
context.Employee.AddRange(
new Models.Employee() { Name = "Ramesh", IsDeleted = false },
new Models.Employee() { Name = "Suresh", IsDeleted = false }
);
context.SaveChanges();
}

public void Dispose()
{
context.Dispose();
}
}
}

IDisposable:- IDisposable is an interface that implements Dispose() and that is used to reduce memory from the garbage collectors.
EnsureDeleted:- It is used to drop the database if it already exists.
EnsureCreated:- It is used to create a database if it does not exist and initialize schema also.

How to perform xUnit testing?

First, we create a sample web API Project with basic crud operations using the EF Core code first approach.

How to perform xUnit testing

Configure new project
My machine runs .Net 5.0, so I’m using the latest template, but we are free to choose which version we prefer.

Create the Model Folder and inside will configure the Model class and DbContext for the EntityFramework Core Code First approach set up.

Model Folder

Employee.cs

using System; 
using System.Collections.Generic; 
using System.ComponentModel.DataAnnotations; 
using System.Linq; 
using System.Threading.Tasks; 

namespace UnitTest_Mock.Model 
{ 
public class Employee 
{ 
[Key] 
public int Id { get; set; } 
public string Name { get; set; } 
public string Desgination { get; set; } 
} 
}

AppDbContext.cs

using Microsoft.EntityFrameworkCore; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Threading.Tasks; 

namespace UnitTest_Mock.Model 
{ 
public partial class AppDbContext : DbContext 
{ 
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) 
{ 

} 
public DbSet<Employee> Employees { get; set; } 
} 
}

Let’s set up the connection string to perform the code first operations.

appsettings.json

{ 
"Logging": { 
"LogLevel": { 
"Default": "Information", 
"Microsoft": "Warning", 
"Microsoft.Hosting.Lifetime": "Information" 
} 
}, 
"AllowedHosts": "*", 
"ConnectionStrings": { 
"myconn": "server=Your server name; database=UnitTest;Trusted_Connection=True;" 
} 
}

Startup.cs

using Microsoft.AspNetCore.Builder; 
using Microsoft.AspNetCore.Hosting; 
using Microsoft.AspNetCore.HttpsPolicy; 
using Microsoft.AspNetCore.Mvc; 
using Microsoft.EntityFrameworkCore; 
using Microsoft.Extensions.Configuration; 
using Microsoft.Extensions.DependencyInjection; 
using Microsoft.Extensions.Hosting; 
using Microsoft.Extensions.Logging; 
using Microsoft.OpenApi.Models; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Threading.Tasks; 
using UnitTest_Mock.Model; 
using UnitTest_Mock.Services; 

namespace UnitTest_Mock 
{ 
public class Startup 
{ 
public Startup(IConfiguration configuration) 
{ 
Configuration = configuration; 
} 

public IConfiguration Configuration { get; } 
public void ConfigureServices(IServiceCollection services) 
{ 
services.AddControllers(); 
services.AddSwaggerGen(c => 
{ 
c.SwaggerDoc("v1", new OpenApiInfo { Title = "UnitTest_Mock", Version = "v1" }); 
}); 
} 
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 
{ 
if (env.IsDevelopment()) 
{ 
app.UseDeveloperExceptionPage(); 
app.UseSwagger(); 
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "UnitTest_Mock v1")); 
}
app.UseHttpsRedirection(); 
app.UseRouting(); 
app.UseAuthorization(); 
app.UseEndpoints(endpoints => 
{ 
endpoints.MapControllers(); 
}); 
} 
} 
}

Create the tables by using the below commands in the console.

Step 1

To create a migration script
PM> Add-Migration ‘Initial’

Step 2

To execute the script in SQL Db
PM> update-database
Create a Services folder where we perform our business logic for all the operations.
update database

EmployeeService.cs

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Threading.Tasks; 
using UnitTest_Mock.Model; 
using Microsoft.EntityFrameworkCore; 

namespace UnitTest_Mock.Services 
{ 
public class EmployeeService : IEmployeeService 
{ 
private readonly AppDbContext _appDbContext; 
public EmployeeService(AppDbContext appDbContext) 
{ 
_appDbContext = appDbContext; 
}
public async Task<string> GetEmployeebyId(int EmpID) 
{ 
var name = await _appDbContext.Employees.Where(c=>c.Id == EmpID).Select(d=> d.Name).FirstOrDefaultAsync(); 
return name; 
} 
public async Task<Employee> GetEmployeeDetails(int EmpID) 
{ 
var emp = await _appDbContext.Employees.FirstOrDefaultAsync(c => c.Id == EmpID); 
return emp; 
} 
} 
}

IEmployeeService.cs

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Threading.Tasks; 
using UnitTest_Mock.Model; 

namespace UnitTest_Mock.Services 
{ 
public interface IEmployeeService 
{ 
Task<string> GetEmployeebyId(int EmpID); 
Task<Employee> GetEmployeeDetails(int EmpID); 
} 
}

Create API methods for those services in the controller class.

EmployeeController.cs

using Microsoft.AspNetCore.Mvc; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Threading.Tasks; 
using UnitTest_Mock.Model; 
using UnitTest_Mock.Services; 

namespace UnitTest_Mock.Controllers 
{ 
[Route("api/[controller]")] 
[ApiController] 
public class EmployeeController : ControllerBase 
{ 
private readonly IEmployeeService _employeeService; 
public EmployeeController(IEmployeeService employeeService) 
{ 
_employeeService = employeeService; 
} 
[HttpGet(nameof(GetEmployeeById))] 
public async Task<string> GetEmployeeById(int EmpID) 
{ 
var result = await _employeeService.GetEmployeebyId(EmpID); 
return result; 
} 
[HttpGet(nameof(GetEmployeeDetails))] 
public async Task<Employee> GetEmployeeDetails(int EmpID) 
{ 
var result = await _employeeService.GetEmployeeDetails(EmpID); 
return result; 
} 

} 
}

We need to create a test project for unit testing as per the following steps.

  • Right-click on the Solution
  • Click on Add – New project
  • Search for X-Unit Test project.

X-Unit Test project
configure your new project

Choose the target framework the same as where we have used it in our API project.
Additional Information

Install the Moq package inside this unit test project.

What is Moq:-
Moq is a mocking framework for C#/. NET. In unit testing, it isolates your class under test from its dependencies and ensures that its methods are calling the right ones.

Create a class inside this Test project to define all our respective test cases but before that, we have to insert data into the table which we have created. Open the SQL Server and insert dummy data into the employee table.

EmployeeTest.cs

using Moq; 
using UnitTest_Mock.Controllers; 
using UnitTest_Mock.Model; 
using UnitTest_Mock.Services; 
using Xunit; 

namespace UnitTesting 
{ 
public class EmployeeTest 
{ 
public Mock<IEmployeeService> mock = new Mock<IEmployeeService>(); 

private readonly DatabaseFixture _fixture;

[Fact] 
public async void GetEmployeebyId() 
{ 
EmployeeController EmpController = new EmployeeController(_fixture.context);
var Id = 1;

var data = EmpController.GetEmployeebyId(Id);

Assert.IsType<SingleResult<Employee>>(data);

} 
[Fact] 
public async void GetEmployeeDetails() 
{ 
var employeeDTO = new Employee() 
{ 
Id = 1, 
Name = "JK", 
Desgination = "SDE" 
}; 
mock.Setup(p => p.GetEmployeeDetails(1)).ReturnsAsync(employeeDTO); 
EmployeeController emp = new EmployeeController(mock.Object); 
var result = await emp.GetEmployeeDetails(1); 
Assert.True(employeeDTO.Equals(result)); 
} 
} 
}

In the above test controller for the GetEmployeeById method, we use the fixture class so the user can get an idea of how to use the fixture class.
setting up the mock for our API business services under the controller level to check the result and compare it with user-defined values.

we can debug the test cases to check the output in running mode.

Verify whether all test cases passed or failed.
Click on View in the Top left
Click on Test explorer.

Run all the test cases
The above image shows all our test cases passed, as well as their duration.

Conclusion

In the end, if we have knowledge about how to do the unit tests then it’s very helpful for finding the bugs in the code and it’s also helpful for quality code.

References:-

Unit Testing With Xunit in asp.net Core
https://www.c-sharpcorner.com/article/unit-testing-using-xunit-and-moq-in-asp-net-core/