DEV Community

Cover image for Unit Test with .Net 6 with xUnit and MOQ
Mohamad Lawand
Mohamad Lawand

Posted on

Unit Test with .Net 6 with xUnit and MOQ

In this article we are going to learn about Unit Test and how it can be implemented in .Net 6.

You can watch the full video on Youtube

As well you can find the source code available on GitHub
https://github.com/mohamadlawand087

What is Unit Test?

A unit test is the smallest piece of code that can be logically isolated in a system, usually we think of the smallest piece of logically isolated code as our functions. With this small piece of code we can ran automated tests which make sure our code is always outputting the correct outcome.

Why should i test my code?

  • Saves time: in some cases it will remove the need for manual testing
  • Automation: the ability to retest changed code on the fly
  • Efficient Code: make sure that all potential scenarios are covered and to make Unit Test possible we need to structure our code in away where we can actually unit test it. Which means we need to follow certain patterns like SOLID.
  • Documentation: Help us understand the logic behind our methods
  • Quality: Improve the quality of our code base, help us avoid adding tech debt as much as possible
  • Reliable: When tests are passing, and being executed automatically we can have a more smoother releases and more frequent

Which Unit test to use?

  • msTest -mature, slow
  • NUnit - mature, feature packed, fast
  • xUnit.net - new, fast

Create console application

dotnet new console -n "TestingBasic"
Enter fullscreen mode Exit fullscreen mode

We will need to install some nuget packages

dotnet add package Microsoft.NET.Test.Sdk 
dotnet add package xunit 
dotnet add package xunit.runner.visualstudio 
dotnet add package coverlet.collector 
Enter fullscreen mode Exit fullscreen mode

Creating our UserManagement functionality, create a class called UserManagement and add the following

namespace TestingBasics.Functionalities;

public record User(string FirstName, string LastName)
{
    public int Id { get; init; }
    public DateTime CreatedDate { get; init; } = DateTime.UtcNow;
    public string Phone { get; set; } = "+44 ";
    public bool VerifiedEmail { get; set; } = false;
}

public class UserManagement
{
    private readonly List<User> _users = new ();
    private int idCounter = 1;

    public IEnumerable<User> AllUsers => _users;

    public void Add(User user)
    {
        _users.Add(user with {Id = idCounter++});
    }

    public void UpdatePhone(User user)
    {
        var dbUser = _users.First(x => x.Id == user.Id);

        dbUser.Phone = user.Phone;
    }

    public void VerifyEmail(int id)
    {
        var dbUser = _users.First(x => x.Id == id);

        dbUser.VerifiedEmail = true;
    }
}
Enter fullscreen mode Exit fullscreen mode

Although we can have the tests and the app in the same project, its recommended to have the test in a separate application which leads to better separation of concerns

We create our new xunit project with the following command

dotnet new xunit -n "TestingBasics.Test"
Enter fullscreen mode Exit fullscreen mode

Once the project has been created successfully, we need to add reference to our application

dotnet add TestingBasics.Test/TestingBasics.Test.csproj reference TestingBasics/TestingBasics.csproj
Enter fullscreen mode Exit fullscreen mode

Now create a new class called UserManagementTest and we add the following

using System.Linq;
using TestingBasics.Functionalities;
using Xunit;

namespace TestingBasics.Test;

public class UserManagementTest
{
    [Fact]
    public void Add_CreateUser()
    {
        // Arrange
        var userManagement = new UserManagement();

        // Act
        userManagement.Add(new(
                "Mohamad", "Lawand"
        ));

        // Assert
        var savedUser = Assert.Single(userManagement.AllUsers);
        Assert.NotNull(savedUser);
        Assert.Equal("Mohamad", savedUser.FirstName);
        Assert.Equal("Lawand", savedUser.LastName);
        Assert.NotEmpty(savedUser.Phone);
        Assert.False(savedUser.VerifiedEmail);
    }

    [Fact]
    public void Verify_VerifyEmailAddress()
    {
        // Arrange
        var userManagement = new UserManagement();

        // Act
        userManagement.Add(new(
                "Mohamad", "Lawand"
        ));

        var firstUser = userManagement.AllUsers.ToList().First();
        userManagement.VerifyEmail(firstUser.Id);

        // Assert
        var savedUser = Assert.Single(userManagement.AllUsers);
        Assert.True(savedUser.VerifiedEmail);
    }

    [Fact]
    public void Update_UpdateMobileNumber()
    {
        // Arrange
        var userManagement = new UserManagement();

        // Act
        userManagement.Add(new(
                "Mohamad", "Lawand"
        ));

        var firstUser = userManagement.AllUsers.ToList().First();
        firstUser.Phone = "+4409090909090";
        userManagement.UpdatePhone(firstUser);

        // Assert
        var savedUser = Assert.Single(userManagement.AllUsers);
        Assert.Equal("+4409090909090",savedUser.Phone);
    }
}
Enter fullscreen mode Exit fullscreen mode

Make sure you have the latest version Microsoft.NET.Test.Sdk version 17.0.0 open your TestingBasics.Test.csproj and check the version in case its lower then 17.0.0 update it to the latest version

Now its time to run our test

dotnet test
Enter fullscreen mode Exit fullscreen mode

Mock

Now let us take this to the next step and check how we can utilise Mock, so What is Mocking? When a service depends on another service and we want to test that service.

Instead of doing the full initialisation process of the second service we can Mock it (Pretend as if it was fully functional) and we can run our tests based on that.

Let us create a new class called ShoppingCart and add the following

namespace TestingBasics.Functionalities;

// Defining a product
public record Product(int Id, string Name, double price);

// Db Service
public interface IDbService
{
    bool SaveShoppingCartItem(Product prod);
    bool RemoveShoppingCartItem(int? id);
}

// Shopping Cart functionality
public class ShoppingCart
{
    private IDbService _dbService;

    public ShoppingCart(IDbService dbService)
    {
        _dbService = dbService;
    }

    public bool AddProduct(Product? product)
    {
        if(product == null)
            return false;

        if(product.Id == 0)
            return false;

        _dbService.SaveShoppingCartItem(product);
        return true;    
    }

    public bool DeleteProduct(int? id)
    {
        if(id == null)
            return false;

        if(id == 0)
            return false;

        _dbService.RemoveShoppingCartItem(id);
        return true;    
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let us start creating unit test for these functionalities. As you can see this functionality has a dependency on a db service, which means we need to Mock that service so we can test the implementations that we have written.

Will create a new class inside our Test classlib called ShoppingCartTest. And we will start implementing manual Mock instead of utilising any library to see how thing work in details once we understand the concept of Mocking we can abstract it to utilise a library

using System.Linq;
using TestingBasics.Functionalities;
using Xunit;
using System;

namespace TestingBasics.Test;

public class ShoppingCartTest
{
    public class DbServiceMock : IDbService
    {
        public bool ProcessResult { get; set; }
        public Product ProductBeingProcessed {get;set;}
        public int ProductIdBeingProcessed { get; set; }

        public bool RemoveShoppingCartItem(int? id)
        {
            if(id != null)
            ProductIdBeingProcessed = Convert.ToInt32(id);
            return ProcessResult;
        }

        public bool SaveShoppingCartItem(Product prod)
        {
            ProductBeingProcessed = prod;
            return ProcessResult;
        }
    }

    [Fact]
    public void AddProduct_Success()
    {
        var dbMock = new DbServiceMock();
        dbMock.ProcessResult = true;
        // Arrange
        ShoppingCart shoppingCart = new (dbMock);

        // Act
        var product = new Product(1, "Shoes", 200);
        var result = shoppingCart.AddProduct(product);

        // Assert
        Assert.True(result);
        Assert.Equal(product, dbMock.ProductBeingProcessed);
    }

    [Fact]
    public void AddProduct_Failure_InvalidPayload()
    {
         var dbMock = new DbServiceMock();
        dbMock.ProcessResult = false;

        // Arrange
        ShoppingCart shoppingCart = new (dbMock);

        // Act
        var result = shoppingCart.AddProduct(null);

        // Assert
        Assert.False(result);
    }

    [Fact]
    public void RemoveProduct_Success()
    {
        var dbMock = new DbServiceMock();
        dbMock.ProcessResult = true;
        // Arrange
        ShoppingCart shoppingCart = new (dbMock);

        // Act
        var product = new Product(1, "Shoes", 200);
        var result = shoppingCart.DeleteProduct(product.Id);

        // Assert
        Assert.True(result);
        Assert.Equal(product.Id, dbMock.ProductIdBeingProcessed);
    }

    [Fact]
    public void RemoveProduct_Failed()
    {
        var dbMock = new DbServiceMock();
        dbMock.ProcessResult = false;
        // Arrange
        ShoppingCart shoppingCart = new (dbMock);

        // Act
        var result = shoppingCart.DeleteProduct(null);

        // Assert
        Assert.False(result);
    }

    [Fact]
    public void RemoveProduct_Failed_InvalidId()
    {
        var dbMock = new DbServiceMock();
        dbMock.ProcessResult = false;
        // Arrange
        ShoppingCart shoppingCart = new (dbMock);

        // Act
        var result = shoppingCart.DeleteProduct(0);

        // Assert
        Assert.False(result);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now that we taken a taste of the how much manual work we need to do a manual mock of one of our service, its time to check how a mocking library will be able to help us.

We are going to be using the Moq nuget package, so lets add it to our project by installing the following nuget

dotnet add package Moq 
Enter fullscreen mode Exit fullscreen mode

Now that have installed the package its time for us to implement it inside our code, let us update the unit test code to the following

using System.Linq;
using TestingBasics.Functionalities;
using Xunit;
using System;
using Moq;

namespace TestingBasics.Test;

public class ShoppingCartTest
{
    public readonly Mock<IDbService> _dbServiceMock = new();

    [Fact]
    public void AddProduct_Success()
    {
        var product = new Product(1, "Shoes", 200);
        _dbServiceMock.Setup(x => x.SaveShoppingCartItem(product)).Returns(true);
        // Arrange
        ShoppingCart shoppingCart = new (_dbServiceMock.Object);

        // Act
        var result = shoppingCart.AddProduct(product);

        // Assert
        Assert.True(result);
        _dbServiceMock.Verify(x => x.SaveShoppingCartItem(It.IsAny<Product>()), Times.Once);
    }

    [Fact]
    public void AddProduct_Failure_InvalidPayload()
    {

        // Arrange
        ShoppingCart shoppingCart = new (_dbServiceMock.Object);

        // Act
        var result = shoppingCart.AddProduct(null);

        // Assert
        Assert.False(result);
        _dbServiceMock.Verify(x => x.SaveShoppingCartItem(It.IsAny<Product>()), Times.Never);
    }

    [Fact]
    public void RemoveProduct_Success()
    {
        var product = new Product(1, "Shoes", 200);
        _dbServiceMock.Setup(x => x.RemoveShoppingCartItem(product.Id)).Returns(true);

        // Arrange
        ShoppingCart shoppingCart = new (_dbServiceMock.Object);

        // Act
        var result = shoppingCart.DeleteProduct(product.Id);

        // Assert
        Assert.True(result);
        _dbServiceMock.Verify(x => x.RemoveShoppingCartItem(It.IsAny<int>()), Times.Once);
    }

    [Fact]
    public void RemoveProduct_Failed()
    {
        _dbServiceMock.Setup(x => x.RemoveShoppingCartItem(null)).Returns(false);

        // Arrange
        ShoppingCart shoppingCart = new (_dbServiceMock.Object);

        // Act
        var result = shoppingCart.DeleteProduct(null);

        // Assert
        Assert.False(result);
        _dbServiceMock.Verify(x => x.RemoveShoppingCartItem(null), Times.Never);
    }

    [Fact]
    public void RemoveProduct_Failed_InvalidId()
    {
        _dbServiceMock.Setup(x => x.RemoveShoppingCartItem(null)).Returns(false);

        // Arrange
        ShoppingCart shoppingCart = new (_dbServiceMock.Object);

        // Act
        var result = shoppingCart.DeleteProduct(0);

        // Assert
        Assert.False(result);
        _dbServiceMock.Verify(x => x.RemoveShoppingCartItem(null), Times.Never);
    }
}
Enter fullscreen mode Exit fullscreen mode

Discussion (0)