How to do Unit Testing for the Core Api Project with Repository

  1. How to setup VS Code environment for ASP.NET Core Project
  2. Use code first to connect database in .Net Core API
  3. Use the Repository pattern in .Net core
  4. Create .Net Core Api controller for CRUD with repository
  5. Use the Serilog in .Net Core
  6. Create Angular Project and Communicate with .Net Core
  7. Create Paging Data from .Net Core Api
  8. How to do Unit Testing for the Core Api Project with Repository

1. Introduction

We have created a .Net Core Api project before, this post will discuss how to do the unit testing for the project.

First question, why should we do the unit testing?

Here are some key reasons why unit testing is important and worth investing:

Do you want to be a good trading in cTrader?   >> TRY IT! <<

Finds bugs early – Unit tests help catch bugs early in the development cycle, when they are less expensive to fix.

Improves design – Writing unit tests forces you to think about how your code will be used and drive more modular, reusable code.

Facilitates change – Good unit tests allow developers to refactor code quickly and verifiably. Tests help ensure changes don’t break existing functionality.

Reduces risks – Tests provide a safety net to catch edge cases and errors. They build confidence that refactoring and changes won’t introduce hard-to-find bugs.

Documents code – Unit tests document how production code should be used. They are live examples of how to call various methods.

Enables automation – Automated testing catches issues quickly and frees developers from manual testing. Tests can be run on every code change.

Improves collaboration – Unit tests enable multiple developers to collaborate on code using a shared suite of tests to ensure changes don’t cause issues.

Avoids technical debt – Lack of tests creates a technical debt that slows future development. Writing tests avoid this debt.

2. Create the Unit Testing Project

As I have mentioned before, I like to use VS Code, so I will also base on VS Code and xUnit Test to demonstrate how to do it! 🙂

If you have installed the VS Code extension vscode-solution-explorer then you can easy to create a xUnit Test project from the solution explorer. Just right click the solution and “Add a new project”

then use the xUnit Test Project template, and input the project name MyDemo.Test, then will be created a xUnit Test project

3. Mock Framework

We need to use a mock framework for creating fake objects that simulate the behavior of real objects for testing. So we can install Moq from Nuget for our testing project

4. Create the testing cases

4.1. Mock the Repository

Before we start, we need to create the mock object for repository. Because the mock framework needs to inject into the function logic for testing, so we must use the interfaces programming for the project (like a repository pattern, so this is one of the benefits of using a repository pattern :)).

We can create a MockIUserRepository class to handle user repository testing

//MyDemo.Test/Mocks/MockIUserRepository.cs

using System.Linq.Expressions;
using Moq;
using MyDemo.Core.Data.Entity;
using MyDemo.Core.Repositories;

namespace MyDemo.Test.Mocks
{
    public class MockIUserRepository
    {
        //todo...
    }
}

because we use IQueryable for GetAll in our repository method

public IQueryable<T> GetAll() => _context.Set<T>().AsNoTracking();

so we also need to return the same object for a mock object, then we create the user data as below

public static IQueryable<User> GetUsers()
{
    var users = new List<User>() {
    new User() {
        Id = 100,
        Name = "Unit Tester 1",
        Email = "unit.tester1@abc.com",
        IsActive = true,
        CreatedAt = DateTime.Now.AddDays(-2),
        UpdatedAt = DateTime.Now.AddDays(-2)
    },
    new User() {
        Id = 200,
        Name = "Unit Tester 2",
        Email = "unit.tester2@abc.com",
        IsActive = true,
        CreatedAt = DateTime.Now.AddDays(-1),
        UpdatedAt = DateTime.Now.AddDays(-1)
    }
    }.AsQueryable();

    return users;
}

then we create the mock instance for IUserRepository

var mock = new Mock<IUserRepository>();

setup the mock instance to return a list of users when the GetAll method is called

//get the fake user data
var users = GetUsers(); 
//setup the GetAll and return the user data
mock.Setup(u => u.GetAll()).Returns(()=> users);

setup the GetItemWithConditionAsync and GetWithConditionAsync methods, because these methods need to pass an expression parameter for query data, so we also need to use the Expression<Func<User, bool> for mock the parameter, and this is an async method, so need to use ReturnsAsync for return the value

mock.Setup(u => u.GetItemWithConditionAsync(It.IsAny<Expression<Func<User, bool>>>())).
        ReturnsAsync((Expression<Func<User, bool>> expr) => users.FirstOrDefault(expr));

mock.Setup(u => u.GetWithConditionAsync(It.IsAny<Expression<Func<User, bool>>>())).
                ReturnsAsync((Expression<Func<User, bool>> expr) => users.Where(expr));

the complete codes as below

//MyDemo.Test/Mocks/MockIUserRepository.cs

using System.Linq.Expressions;
using Moq;
using MyDemo.Core.Data.Entity;
using MyDemo.Core.Repositories;

namespace MyDemo.Test.Mocks
{
    public class MockIUserRepository
    {
        public static Mock<IUserRepository> GetMock()
        {
            var mock = new Mock<IUserRepository>();

            var users = GetUsers();

            mock.Setup(u => u.GetAll()).Returns(()=> users);

            mock.Setup(u => u.GetItemWithConditionAsync(It.IsAny<Expression<Func<User, bool>>>())).
                ReturnsAsync((Expression<Func<User, bool>> expr) => users.FirstOrDefault(expr));

            mock.Setup(u => u.GetWithConditionAsync(It.IsAny<Expression<Func<User, bool>>>())).
                ReturnsAsync((Expression<Func<User, bool>> expr) => users.Where(expr));

            return mock;
        }

        public static IQueryable<User> GetUsers()
        {
            var users = new List<User>() {
            new User() {
                Id = 100,
                Name = "Unit Tester 1",
                Email = "unit.tester1@abc.com",
                IsActive = true,
                CreatedAt = DateTime.Now.AddDays(-2),
                UpdatedAt = DateTime.Now.AddDays(-2)
            },
            new User() {
                Id = 200,
                Name = "Unit Tester 2",
                Email = "unit.tester2@abc.com",
                IsActive = true,
                CreatedAt = DateTime.Now.AddDays(-1),
                UpdatedAt = DateTime.Now.AddDays(-1)
            }
            }.AsQueryable();
            return users;
        }
    }
}

4.2. GetUsers testing case

Ok, now we can create the first testing case to get all user data. Get the mock instance and call the repository GetAll method

[Fact]
public void Test1_GetUsers()
{
    //get the mock instance
    var mockUserRepository = MockIUserRepository.GetMock();
    //call the repository to get user data
    var users = mockUserRepository.Object.GetAll();

    //test the result

    //the users should not be null
    Assert.NotNull(users);
    //the users should be an IQueryable<User> object
    Assert.IsAssignableFrom<IQueryable<User>>(users);
    //should be only 2 items in the user list (this is the fake data that we created)
    Assert.Equal(2, users.Count());
}

VS Code can be very helpful to add the actions on the testing case method, you can click the Run Test or Debug Test to run the test

if everything is ok, you will see the testing result in terminal

4.3. Get user by Id testing case

This time we want to test the method GetUser of UserController. We need to create the user controller instance first, as you know there is a constructor for receiving two parameters in the user controller, that’s means we also need to pass these when new it

public UserController(IUserRepository userRepository, ILogger<User> logger)
{
    this._userRepository = userRepository;
    this._logger = logger;
}

but how should we pass the repository and logger objects? Ok, we can also mock them:

//get the user repository mock instance
var mockUserRepository = MockIUserRepository.GetMock();
//create the ILogger mock instance
var logger = Mock.Of<ILogger<User>>();

now we can new a user controller instance

var userController = new UserController(mockUserRepository.Object, logger);

call the GetUser from controller, because the fake user id is 100 and 200, so we try to pass 100 to get a user

//get the user data by id
var result = userController.GetUser(100);

//convert the result to ObjectResult so that we can check the status codes
var objResult = result.Result.Result as ObjectResult;

//convert the result value to our define's ApiResult object, and we can get the user data
var apiResult = objResult.Value as ApiResult<User>;

//test the result values
Assert.NotNull(result);
Assert.Equal(StatusCodes.Status200OK, objResult.StatusCode);
Assert.IsAssignableFrom<User>(apiResult.Data);
Assert.Equal("Unit Tester 1", apiResult.Data.Name);

the complete codes as below

[Fact]
public void Test2_GetUserById()
{
    var mockUserRepository = MockIUserRepository.GetMock();

    //mock the logger
    var logger = Mock.Of<ILogger<User>>();

    var userController = new UserController(mockUserRepository.Object, logger);

    var result = userController.GetUser(100);
    var objResult = result.Result.Result as ObjectResult;
    var apiResult = objResult.Value as ApiResult<User>;

    Assert.NotNull(result);
    Assert.Equal(StatusCodes.Status200OK, objResult.StatusCode);
    Assert.IsAssignableFrom<User>(apiResult.Data);
    Assert.Equal("Unit Tester 1", apiResult.Data.Name);
}

4.4. Create user testing case

You should know the logic and concept, so I just show you the codes below

[Fact]
public void Test3_CreateUser()
{
    var mock = MockIUserRepository.GetMock();
    //mock the logger
    var logger = Mock.Of<ILogger<User>>();
    var userController = new UserController(mock.Object, logger);

    //call the Post method to create a user
    var result = userController.PostUser(new User()
    {
        Id = 300,
        Name = "new user",
        Email = "newTester@abc.com",
        IsActive = true,
        CreatedAt = DateTime.Now,
        UpdatedAt = DateTime.Now
    });

    var objResult = result.Result.Result as ObjectResult;
    var apiResult = objResult.Value as ApiResult<User>;

    Assert.NotNull(result);
    Assert.Equal(StatusCodes.Status200OK, objResult.StatusCode);
    Assert.Equal(true, apiResult.Success);
    Assert.Equal(300, apiResult.Data.Id);
}

4.5. Update user testing case

The codes as below

[Fact]
public void Test4_UpdateUser()
{
    var mock = MockIUserRepository.GetMock();
    //mock the logger
    var logger = Mock.Of<ILogger<User>>();
    var userController = new UserController(mock.Object, logger);

    var result = userController.PutUser(new User()
    {
        Id = 100,
        Name = "update user",
        Email = "updateTester@abc.com",
        IsActive = true,
        CreatedAt = DateTime.Now,
        UpdatedAt = DateTime.Now
    });

    var objResult = result.Result.Result as ObjectResult;
    var apiResult = objResult.Value as ApiResult<User>;

    Assert.NotNull(result);
    Assert.Equal(StatusCodes.Status200OK, objResult.StatusCode);
    Assert.Equal(true, apiResult.Success);
    Assert.Equal("update user", apiResult.Data.Name);
}

4.6. Delete user testing case

Because there is no user data returned by the delete function, so we just need to check the success status from the Api

[Fact]
public void Test5_DeleteUserById()
{
    var mock = MockIUserRepository.GetMock();
    //mock the logger
    var logger = Mock.Of<ILogger<User>>();
    var userController = new UserController(mock.Object, logger);

    var result = userController.DeleteUser(100);
    var objResult = result.Result as ObjectResult;
    var apiResult = objResult.Value as ApiResult<Object>;

    Assert.NotNull(result);
    Assert.Equal(StatusCodes.Status200OK, objResult.StatusCode);

    //check the success whether is true
    Assert.Equal(true, apiResult.Success);
}

In the end, we can run all tests on the class level

and find the result below

5. Summarize

We learned how to do the unit test with the xUnit Test and how to mock the data. There is an important thing you should know if you want to mock data for testing, you must use the interface programming and dependency injection, so if you call the get user method directly from a help method, then you can’t mock it for testing, you can put the method into service and implement from an interface.

The other thing that needs to note is that must use the same parameter type with your method when you setup a mock instance.

Please let me know if you have any ideas for that! 😀

Loading

Views: 36
Total Views: 612 ,

Leave a Reply

Your email address will not be published. Required fields are marked *