Introduction
In the early stages of software development, the smallest components are tested using a software design pattern called unit testing.
Prior to transferring to the production environment and QA Team, unit testing is done to validate the functionality that is to provide the intended output.
It facilitates the early detection of problems in the software development cycle.
When using the.NET Framework, there are numerous unit test tools available, including xUnit, NUnit, and many others.
xUnit
A unit testing framework for.NET development called xUnit is free and open-source.
Numerous capabilities of xUnit enable the creation of clear and effective unit test cases.
It gives a framework for creating our own characteristics and has several attributes, like Fact, Theory, and many more, to help test cases be written clearly and successfully.
Attributes of xUnit
In.NET, xUnit uses the [Fact] attribute to identify the unit test function.
[Fact] public void EvenNumberTest() { //Arrange var num = 6; //Act bool result = Mathematics.IsEvenNumber(num); //Assert Assert.True(result); }
The test method’s [theory] element is used to provide arguments.
[Theory] [InlineData(5)] public void OddNumberTest(int num) { //Act bool result = Mathematics.IsOddNumber(num); //Assert Assert.True(result); }
Test Pattern
Writing unit test cases in the arrange-act-assert format makes them cleaner and easier to read.
Arrange
We set up and declare a few inputs and configuration variables in the arrange section.
Act
We included key components and functionality, such as method calls, API calls, and similar items, in the Act section.
Assert
Check the expected outputs to ensure they comply with our functional requirements.
Moq
- In essence, Moq is the library that is utilized for mocking.
- Assuming that our application depends on one or more services, we can utilize the Moq library to mock specific classes and functions with fictitious data instead of having to initialize everything linked to those services.
Step 1
Create a new .NET Core API Project
Step 2
Configure your project
Step 3
Provide additional information about your project
Step 4
Project Structure
Step 5
Install Following NuGet Packages
Step 6
Create the Models folder and create a new class Product
namespace UnitTestMoqFinal.Models { public class Product { public int ProductId { get; set; } public string ProductName { get; set; } public string ProductDescription { get; set; } public int ProductPrice { get; set; } public int ProductStock { get; set; } } }
Step 7
For data manipulation, create DbContextClass in the Data folder.
using Microsoft.EntityFrameworkCore; using UnitTestMoqFinal.Models; namespace UnitTestMoqFinal.Data { public class DbContextClass : DbContext { protected readonly IConfiguration Configuration; public DbContextClass(IConfiguration configuration) { Configuration = configuration; } protected override void OnConfiguring(DbContextOptionsBuilder options) { options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")); } public DbSet<Product> Products { get; set; } } }
Step 8
Later, inside the Services folder, create the IProductService and ProductService classes for dependency injection and abstraction.
using UnitTestMoqFinal.Models; namespace UnitTestMoqFinal.Services { public interface IProductService { public IEnumerable<Product> GetProductList(); public Product GetProductById(int id); public Product AddProduct(Product product); public Product UpdateProduct(Product product); public bool DeleteProduct(int Id); } }
Create ProductService class
using UnitTestMoqFinal.Data; using UnitTestMoqFinal.Models; namespace UnitTestMoqFinal.Services { public class ProductService : IProductService { private readonly DbContextClass _dbContext; public ProductService(DbContextClass dbContext) { _dbContext = dbContext; } public IEnumerable<Product> GetProductList() { return _dbContext.Products.ToList(); } public Product GetProductById(int id) { return _dbContext.Products.Where(x => x.ProductId == id).FirstOrDefault(); } public Product AddProduct(Product product) { var result = _dbContext.Products.Add(product); _dbContext.SaveChanges(); return result.Entity; } public Product UpdateProduct(Product product) { var result = _dbContext.Products.Update(product); _dbContext.SaveChanges(); return result.Entity; } public bool DeleteProduct(int Id) { var filteredData = _dbContext.Products.Where(x => x.ProductId == Id).FirstOrDefault(); var result = _dbContext.Remove(filteredData); _dbContext.SaveChanges(); return result != null ? true : false; } } }
Step 9
After that, Create a new ProductController
using Microsoft.AspNetCore.Mvc; using UnitTestMoqFinal.Models; using UnitTestMoqFinal.Services; namespace UnitTestMoqFinal.Controllers { [Route("api/[controller]")] [ApiController] public class ProductController : ControllerBase { private readonly IProductService productService; public ProductController(IProductService _productService) { productService = _productService; } [HttpGet("productlist")] public IEnumerable<Product> ProductList() { var productList = productService.GetProductList(); return productList; } [HttpGet("getproductbyid")] public Product GetProductById(int Id) { return productService.GetProductById(Id); } [HttpPost("addproduct")] public Product AddProduct(Product product) { return productService.AddProduct(product); } [HttpPut("updateproduct")] public Product UpdateProduct(Product product) { return productService.UpdateProduct(product); } [HttpDelete("deleteproduct")] public bool DeleteProduct(int Id) { return productService.DeleteProduct(Id); } } }
Step 10
Add connection string inside app setting file
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Data Source=DESKTOP-Server;Initial Catalog=UnitTestMoqFinal;User Id=***;Password=***;" } }
Step 11
Next, register a few services inside Program Class
using UnitTestMoqFinal.Data; using UnitTestMoqFinal.Services; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddScoped<IProductService, ProductService>(); builder.Services.AddDbContext<DbContextClass>(); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
Step 12
Execute the following entity framework command under the main project’s package manager console to add migrations and update the database.
“First” add-migration
update-database
The.NET Core Web API is the focus of this discussion. Let’s build a new Xunit project and use Moq to develop the test cases inside of it.
Step 13
Finally, run your application and you will see swagger UI and API endpoints
Create a new Xunit project
Step 1
Add a new Xunit project inside the existing solution
Step 2
Configure your new project
Step 3
Provide some additional information
Step 4
Install Moq NuGet Package for mocking purpose
Step 5
Create UnitTestController Class
using Moq; using UnitTestMoqFinal.Controllers; using UnitTestMoqFinal.Models; using UnitTestMoqFinal.Services; namespace UnitTestProject { public class UnitTestController { private readonly Mock<IProductService> productService; public UnitTestController() { productService = new Mock<IProductService>(); } [Fact] public void GetProductList_ProductList() { //arrange var productList = GetProductsData(); productService.Setup(x => x.GetProductList()) .Returns(productList); var productController = new ProductController(productService.Object); //act var productResult = productController.ProductList(); //assert Assert.NotNull(productResult); Assert.Equal(GetProductsData().Count(), productResult.Count()); Assert.Equal(GetProductsData().ToString(), productResult.ToString()); Assert.True(productList.Equals(productResult)); } [Fact] public void GetProductByID_Product() { //arrange var productList = GetProductsData(); productService.Setup(x => x.GetProductById(2)) .Returns(productList[1]); var productController = new ProductController(productService.Object); //act var productResult = productController.GetProductById(2); //assert Assert.NotNull(productResult); Assert.Equal(productList[1].ProductId, productResult.ProductId); Assert.True(productList[1].ProductId == productResult.ProductId); } [Theory] [InlineData("IPhone")] public void CheckProductExistOrNotByProductName_Product(string productName) { //arrange var productList = GetProductsData(); productService.Setup(x => x.GetProductList()) .Returns(productList); var productController = new ProductController(productService.Object); //act var productResult = productController.ProductList(); var expectedProductName = productResult.ToList()[0].ProductName; //assert Assert.Equal(productName, expectedProductName); } [Fact] public void AddProduct_Product() { //arrange var productList = GetProductsData(); productService.Setup(x => x.AddProduct(productList[2])) .Returns(productList[2]); var productController = new ProductController(productService.Object); //act var productResult = productController.AddProduct(productList[2]); //assert Assert.NotNull(productResult); Assert.Equal(productList[2].ProductId, productResult.ProductId); Assert.True(productList[2].ProductId == productResult.ProductId); } private List<Product> GetProductsData() { List<Product> productsData = new List<Product> { new Product { ProductId = 1, ProductName = "IPhone", ProductDescription = "IPhone 12", ProductPrice = 55000, ProductStock = 10 }, new Product { ProductId = 2, ProductName = "Laptop", ProductDescription = "HP Pavilion", ProductPrice = 100000, ProductStock = 20 }, new Product { ProductId = 3, ProductName = "TV", ProductDescription = "Samsung Smart TV", ProductPrice = 35000, ProductStock = 30 }, }; return productsData; } } }
- You can see here that we added a reference to our main project before adding references to the current unit test project.
- Inside the function Object() { [native code] }, we create a dummy IProductService instance.
- Then, we create a test case that requires a list of products.
- We then take a list of items from our custom method, which is located at the bottom of the same class.
- Next, create a dummy data set for the product service’s list of products.
- Additionally, because our product controller depends on the product service, we send the product service object within the function Object() { [native code] } of the product controller to resolve some dependencies.
- We invoke the ProductList method of the controller in the act section.
- Finally, we use a few conditions in the assert section to compare the actual and anticipated results.
Similar to this, each test case is completed step-by-step.
Step 6
Next, launch the Test Explorer inside of Visual Studio’s Test Section to examine all the test cases that we’ve written for the UnitTestControllerClass.
Step 7
Final Project Structure
Step 8
In addition, if you want to debug a test case, simply right-click on the test case and select debug after attaching the debugger point inside the test case. Finally, run your test cases to see if they worked successfully or not.
Conclusion
Unit tests and various properties with patterns are now being used. Following then, the use of Moq was addressed. Additionally, the implementation of.NET Core 6 is step-by-step.