Unit Test with .Net 6 with xUnit and MOQ
Tags: dotnet, unittest, csharp, programming
Published: 2022-01-21 10:54:24

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
{% youtube AW7ZcMOtwC8 %}

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

```bash
dotnet new console -n "TestingBasic"
```

We will need to install some nuget packages

```bash
dotnet add package Microsoft.NET.Test.Sdk 
dotnet add package xunit 
dotnet add package xunit.runner.visualstudio 
dotnet add package coverlet.collector 
```

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

```csharp
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;
    }
}
```

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

```bash
dotnet new xunit -n "TestingBasics.Test"
```

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

```bash
dotnet add TestingBasics.Test/TestingBasics.Test.csproj reference TestingBasics/TestingBasics.csproj
```

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

```csharp
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);
    }
}
```

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

```bash
dotnet test
```

### 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

```csharp
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;    
    }
}
```

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

```csharp
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);
    }
}
```

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

```bash
dotnet add package Moq 
```

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

```csharp
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);
    }
}
```