Implementation Of Unit Test Using Xunit And Moq in .NET Core 6 Web API

We are going to discuss unit tests using xUnit and Moq here step-by-step in detail, I suggest you read my following blog for a basic understanding of unit test case

Unit Test using xUnit in .NET Core 6 with the help of VS Code

Introduction

  • Unit Testing is a software design pattern that is used to test the smallest components in the software development phase.
  • Unit Testing is used to validate the functionality which is to create expected output before going to the production environment and QA Team.
  • It helps to detect issues at the early phase of the software development cycle
  • There are many unit test tools that are already present while using .NET Framework like xUnit, NUnit, and many more.

xUnit

  • xUnit is a free and open-source Unit testing framework for .NET development
  • xUnit has many features which provide for writing a clean and good unit test case.
  • It has many attributes like Fact, Theory, and many more to write test cases effectively and cleanly and also provides a mechanism to create our own attribute

Attributes of xUnit

[Fact] attribute is used by xUnit in .NET which identifies the method for unit test

[Fact]
public void EvenNumberTest() {
    //Arrange
    var num = 6;
    //Act
    bool result = Mathematics.IsEvenNumber(num);
    //Assert
    Assert.True(result);
}

[Theory] attribute is used to supply parameters to the test method

[Theory]
[InlineData(5)]
public void OddNumberTest(int num) {
    //Act
    bool result = Mathematics.IsOddNumber(num);
    //Assert
    Assert.True(result);
}

Test Pattern

Arrange-Act-Assert is a great way to write clean and more readable unit test cases

Arrange

In the arrange section we setup and declare some inputs and configuration variable

Act

In the Act section, we put main things and functionality like method calls, API calls, and something like that

Assert

Assert checks expected outputs and check whether they will match our functional requirement or not

Moq

  • Basically, Moq is the library that is used for mocking purposes.
  • Suppose our application is dependent on one or more services at that time we don’t need to initialize all the things related to that we just use the Moq library to mock some classes and functionality with dummy data.

Step 1

Create a new .NET Core API Project

Unit Test using Xunit and Moq

Step 2

Configure your project

Unit Test using Xunit and Moq

Step 3

Provide additional information about your project

Unit Test using Xunit and Moq

Step 4

Project Structure

Unit Test using Xunit and Moq

Step 5

Install Following NuGet Packages

Unit Test using Xunit and Moq

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

Next, Create DbContextClass inside the Data folder for data manipulation

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 on, Create IProductService and ProductService class for abstraction and dependency injection inside the Services folder.

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

Add migrations and update the database using the following entity framework command after executing that into the package manager console under the main project

add-migration "First"

update-database

Step 13

Finally, run your application and you will see swagger UI and API endpoints

Unit Test using Xunit and Moq

This is all about the .NET Core Web API, Let’s create a new Xunit project inside which we use Moq to write the test cases.

Create a new Xunit project

Step 1

Add a new Xunit project inside the existing solution

Unit Test using Xunit and Moq

Step 2

Configure your new project

Unit Test using Xunit and Moq

Step 3

Provide some additional information

Unit Test using Xunit and Moq

Step 4

Install Moq NuGet Package for mocking purpose

Unit Test using Xunit and Moq

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;
        }
    }
}
  • Here, you can see first we add reference of our main project into the current unit test project
  • We mock IProductService and create an instance inside the constructor
  • Next, we write one test case which takes a list of a product
  • Later on, we take a list of products from our custom method which is present in the same class at the bottom
  • Next, set up the list of products for the product service with some mock data
  • Also, our product controller is dependent on product service and because of that, we pass the product service object inside the product controller constructor to resolve some dependencies.
  • In the act section, we call the ProductList method of the controller.
  • Finally, in the assert section, we check actual and expected results using a few conditions

Similarly, all the test cases are worked step by step

Step 6

Next, go to the Test Section at the top of Visual Studio and open the Test Explorer inside that you can see all the test cases which we write inside the UnitTestControllerClass

Step 7

Final Project Structure

Unit Test using Xunit and Moq

Step 8

Finally, run your test cases and check if they will be worked properly or not, also if you want to debug a test case simply right-click on the test case and click on debug after attaching the debugger point inside the test case

Unit Test using Xunit and Moq

Conclusion

We see the introduction of unit tests and some attributes with patterns. After that, discussed Moq and its usage. Also, step-by-step implementation of using .NET Core 6.