How to use Moq and xUnit for Unit Testing Controllers in ASP.NET Core

How to use Moq and xUnit for Unit Testing Controllers in ASP.NET Core

Moq as the name suggest is a mocking framework for .NET. It is used to create fake objects for underlying dependencies like that of an Entity Framework Core. This helps to test the interactions between the method and its dependencies. Moq is very useful when testing a controller that is doing database interactions through EF Core. These interactions can be like Inserting, Deleting, Updating or Reading or records from the database. In this tutorial I will be explaining all these stuffs to you so hold back tight and make sure you go through every part of this tutorial.

The source codes of this tutorial can be downloaded from my GitHub Repository.

Project setup

In this tutorial I will be using the same app that I built in my last tutorial How to perform Unit Testing with xUnit in ASP.NET Core. This app solution file has 2 projects in .NET 5.0, these are:

1. MyAppT

An ASP.NET Core MVC project.

2. TestingProject

A Class Library (.NET Core) project that has 3 packages installed.

  1. xunit
  2. xunit.runner.visualstudio
  3. Microsoft.NET.Test.Sdk
xunit xunit.runner.visualstudio Microsoft.NET.Test.Sdk

I have also added the reference of MyAppT to the TestingProject. You can do the same thing to your project or just go to my previous tutorial and download the source code from there.

Next, let us install Moq package.

Moq Installation

In your TestingProject, Install Moq from NuGet. See the below image:

moq framework installation

In Memory Database

I am going to prepare an in-memory database so that I don’t have to use the real SQL server during testing. For this install the following 2 packages in the MyAppT project.

  1. Microsoft.EntityFrameworkCore
  2. Microsoft.EntityFrameworkCore.InMemory
in-memory database

With this we can go forward to create the Database Context and Models.

Create a new class called Register.cs inside the Models folder of MyAppT. Its code is given below:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace MyAppT.Models
{
    public class Register
    {
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }

        [Range(40, 60)]
        public int Age { get; set; }
    }
}

This class will be used to create, read, update, delete records from the in-memory database. Notice some validation attributes applied to the fields, which are:

  • 1. The Name should be necessary field to fill.
  • 2. The Age should be from 40 to 60 only.

I will later create a test method that will be testing these scenarios to.

Next create the Database Context class inside the same Models folder. Name this class as AppDbContext.cs.

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MyAppT.Models
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        {
        }
        public DbSet<Register> Register { get; set; }
    }
}

The Register.cs class has been added to the Database Context as a property. So now Entity Framework Core operations can be performed over it.

Next, add Database context as a service in the ConfigureServices() method of Startup.cs class (of MyAppT project) as shown below:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppDbContext>(optionsBuilder => optionsBuilder.UseInMemoryDatabase("InMemoryDb"));
    services.AddControllersWithViews();
}

Through this the Database Context is added as a service.

Creating Database Operations Class

Create a new class called Operations.cs inside the Models folder of the “MyAppT” project. This class contains an interface and a class that implements this interface. They will together form the core logic of Creating, Reading, Updating and Deletion of the records from the database.

It’s full code is given below:

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MyAppT.Models
{
    public interface IRegisterRepository
    {
        Task<Register> GetByIdAsync(int id);
        Task<List<Register>> ListAsync();
        Task CreateAsync(Register register);
        Task UpdateAsync(Register register);
        Task DeleteAsync(int id);
    }

    public class RegisterRepository : IRegisterRepository
    {
        private readonly AppDbContext context;

        public RegisterRepository(AppDbContext dbContext)
        {
            context = dbContext;
        }

        public Task<Register> GetByIdAsync(int id)
        {
            return context.Register.FirstOrDefaultAsync(s => s.Id == id);
        }

        public Task<List<Register>> ListAsync()
        {
            return context.Register.ToListAsync();
        }

        public Task CreateAsync(Register register)
        {
            context.Register.Add(register);
            return context.SaveChangesAsync();
        }

        public Task UpdateAsync(Register register)
        {
            context.Entry(register).State = EntityState.Modified;
            return context.SaveChangesAsync();
        }
        public async Task DeleteAsync(int id)
        {
            var r = await GetByIdAsync(id);
            context.Remove(r);
            await context.SaveChangesAsync();
        }
    }
}

Next, go to the Startup.cs class and add this interface as a scoped service.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppDbContext>(optionsBuilder => optionsBuilder.UseInMemoryDatabase("InMemoryDb"));
    services.AddScoped<IRegisterRepository, RegisterRepository>();
    services.AddControllersWithViews();
}

Creating a Simple CRUD Operations feature in ASP.NET Core

I will need to create a controller where CRUD operations will be performed. It will work as shown by the below given video:

So, create a new controller called RegisterController.cs inside the Controllers folder of “MyAppT” project. Provide the IRegisterRepository in it’s constructor’s parameter, this will provide the controller with the RegisterRepository object through Dependency Injection. Check the below highlighted code of this class.

using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using MyAppT.Models;

namespace MyAppT.Controllers
{
    public class RegisterController : Controller
    {
        private IRegisterRepository context;
        public RegisterController(IRegisterRepository appDbContext)
        {
            context = appDbContext;
        }
    }
}
CREATE record feature

Add “Create” actions to the RegisterController where users will be able to create records. The create action code is given below.

using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using MyAppT.Models;

namespace MyAppT.Controllers
{
    public class RegisterController : Controller
    {
        private IRegisterRepository context;
        public RegisterController(IRegisterRepository appDbContext)
        {
            context = appDbContext;
        }

        public IActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Create(Register register)
        {
            if (ModelState.IsValid)
            {
                await context.CreateAsync(register);
                return RedirectToAction("Read");
            }
            else
                return View();
        }
    }
}

Next, add the Create.cshtml Razor View inside the Views ➤ Register folder. Add the following code to it.

@model Register

@{
    ViewData["Title"] = "Create Record";
}

<h1 class="bg-info text-white">Create Record</h1>
<a asp-action="Read" class="btn btn-secondary">View all</a>

<div asp-validation-summary="All" class="text-danger btn-light"></div>

<form method="post">
    <div class="form-group">
        <label asp-for="Name"></label>
        <input type="text" asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Age"></label>
        <input type="text" asp-for="Age" class="form-control" />
        <span asp-validation-for="Age" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary">Create</button>
</form>

The create view provides a form to the user. On filling this form the record will be created in the in-memory database.

I have shown this form in the below image:

create view
READ record feature

All the records stored in the in-memory database will be read and shown on the HTML Table in the view. Add Read action method to the controller. It’s code is given below:

using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using MyAppT.Models;

namespace MyAppT.Controllers
{
    public class RegisterController : Controller
    {
        private IRegisterRepository context;
        public RegisterController(IRegisterRepository appDbContext)
        {
            context = appDbContext;
        }

        //…

        public async Task<IActionResult> Read()
        {
            var rl = await context.ListAsync();
            return View(rl);
        }
    }
}

Next, add a view called Read.cshtml inside the Views ➤ Register folder with the following code:

@model List<Register>

@{
    ViewData["Title"] = "Records";
}

<h1 class="bg-info text-white">Records</h1>
<a asp-action="Create" class="btn btn-secondary">Create</a>

<table class="table table-sm table-bordered">
    <tr>
        <th>Id</th>
        <th>Name</th>
        <th>Age</th>
        <th></th>
        <th></th>
    </tr>
    @foreach (Register r in Model)
    {
        <tr>
            <td>@r.Id</td>
            <td>@r.Name</td>
            <td>@r.Age</td>
            <td>
                <a class="btn btn-sm btn-primary" asp-action="Update" asp-route-id="@r.Id">Update</a>
            </td>
            <td>
                <form asp-action="Delete" asp-route-id="@r.Id" method="post">
                    <button type="submit" class="btn btn-sm btn-danger">
                        Delete
                    </button>
                </form>
            </td>
        </tr>
    }
</table>

The read view will show all the records to the users. See below image:

Read View

The HTML Table also has 2 columns that allow user to update and delete the records. I will take this topic next.

UPDATE record feature

Users will also need to update the records which can be done by the update action method. It’s code is shown highlighted below:

using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using MyAppT.Models;

namespace MyAppT.Controllers
{
    public class RegisterController : Controller
    {
        private IRegisterRepository context;
        public RegisterController(IRegisterRepository appDbContext)
        {
            context = appDbContext;
        }

        //…

        public async Task<IActionResult> Update(int id)
        {
            Register r = await context.GetByIdAsync(id);
            return View(r);
        }

        [HttpPost]
        public async Task<IActionResult> Update(Register register)
        {
            if (ModelState.IsValid)
            {
                await context.UpdateAsync(register);
                ViewBag.Result = "Success";
            }
            return View(register);
        }
    }
}

Add the update view – Update.cshtml inside the Views ➤ Register folder with the following code:

@model Register

@{
    ViewData["Title"] = "Update Record";
}

<h1 class="bg-info text-white">Update Record</h1>
<a asp-action="Read" class="btn btn-secondary">View all</a>
<h2 class="bg-light">@ViewBag.Result</h2>

<div asp-validation-summary="All" class="text-danger btn-light"></div>

<form method="post">
    <div class="form-group">
        <label asp-for="Id"></label>
        <input type="text" asp-for="Id" disabled class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="Name"></label>
        <input type="text" asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Age"></label>
        <input type="text" asp-for="Age" class="form-control" />
        <span asp-validation-for="Age" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary">Update</button>
</form>

The look of this view is like that of the create view. Recall, in the Read view I have provided Update link against each record, on clicking that link the users will be taken to the update view.

DELETE record feature

Finally create Delete action whose code is given below.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using MyAppT.Models;

namespace MyAppT.Controllers
{
    public class RegisterController : Controller
    {
        private IRegisterRepository context;
        public RegisterController(IRegisterRepository appDbContext)
        {
            context = appDbContext;
        }

        //…

        [HttpPost]
        public async Task<IActionResult> Delete(int id)
        {
            await context.DeleteAsync(id);
            return RedirectToAction("Read");
        }
    }
}

Recall that in the Read view I have provided a Delete button against each record. On clicking that button the corresponding record will be deleted.

Testing Controller with Moq and xUnit

Finally, we have arrived on the testing part where I will be writing test methods for the RegisterController that performs CRUD operations. I will make use both Moq for mocking EF Core object and xUnit for creating the unit test.

Start by adding a new class called TestRegister.cs to the TestingProject. In this class I will write test methods for all the 4 actions which are “Create, Read, Update and Delete” one by one.

Unit Tests for “Create” actions

There will be 3 unit tests for both HTTP GET and HTTP POST types of Create action methods. Let us add them one by one.

1. Test_Create_GET_ReturnsViewResultNullModel() for testing HTTP GET Create action

Add the method called Test_Create_GET_ReturnsViewResultNullModel() inside the TestRegister.cs. This method will test the GET type of Create Action method, checking if its return type is ViewResult with a null model. The code is given below:

using System.Threading.Tasks;
using Xunit;
using Microsoft.AspNetCore.Mvc;
using MyAppT.Controllers;
using MyAppT.Models;
using Moq;

namespace TestingProject
{
    public class TestRegister
    {
        [Fact]
        public void Test_Create_GET_ReturnsViewResultNullModel()
        {
            // Arrange
            IRegisterRepository context = null;
            var controller = new RegisterController(context);

            // Act
            var result = controller.Create();

            // Assert
            var viewResult = Assert.IsType<ViewResult>(result);
            Assert.Null(viewResult.ViewData.Model);
        }
    }
}

I have passed on null for the IRegisterRepository interface even through the constructor of the RegisterController requires it. This is done because the Create action method does not use IRegisterRepository.

IRegisterRepository context = null;
var controller = new RegisterController(context);

In the latter test methods, I will be mocking IRegisterRepository interface by using Moq framework.

Next see the assert section where I am checking for the return type to be ViewResult and model to be null.

var viewResult = Assert.IsType<ViewResult>(result);
Assert.Null(viewResult.ViewData.Model);
2. Test_Create_POST_InvalidModelState() for testing HTTP POST Create action

Add the method called Test_Create_POST_InvalidModelState() inside the TestRegister.cs. In this method I will test for invalid model state. This happens in the scenario when the user does not fill, or fills incorrectly, any of the fields of the register form.

The code of this test method is given below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Microsoft.AspNetCore.Mvc;
using MyAppT.Controllers;
using MyAppT.Models;
using Moq;

namespace TestingProject
{
    public class TestRegister
    {
        //…

        [Fact]
        public void Test_Create_POST_InvalidModelState()
        {
            // Arrange
            var r = new Register()
            {
                Id = 4,
                Name = "Test Four",
                Age = 59
            };
            var mockRepo = new Mock<IRegisterRepository>();
            mockRepo.Setup(repo => repo.CreateAsync(It.IsAny<Register>()));
            var controller = new RegisterController(mockRepo.Object);
            controller.ModelState.AddModelError("Name", "Name is required");

            // Act
            var result = controller.Create(r);

            // Assert
            var viewResult = Assert.IsType<ViewResult>(result);
            Assert.Null(viewResult.ViewData.Model);
            mockRepo.Verify();
        }
    }
}

In the arrange section I have used Moq framework to create a mock object for the IRegisterRepository object.

var mockRepo = new Mock<IRegisterRepository>();

The setup of the mocked object is done by calling CreateAsync() method and passing It.IsAny<Register> will match any parameter of type Register provided to it.

I also used Verifiable() method to mark this setup as verifiable this means when mockRepo.Verify() is called then xUnit will verify this setup also.

mockRepo.Setup(repo => repo.CreateAsync(It.IsAny<Register>()));

Next, I made the ModelState invalid to create a condition when user submits the form without filling the name field. This is what this test is written for.

controller.ModelState.AddModelError("Name", "Name is required");

I also created a test Register object called “r” and used it when calling the create action.

var result = await controller.Create(r);

Finally, in the assert section I performed the testing with xUnit.

var viewResult = Assert.IsType<ViewResult>(result);
Assert.Null(viewResult.ViewData.Model);
mockRepo.Verify();
3. Test_Create_POST_ValidModelState for testing HTTP POST Create action

This test method will test the scenario when the form is submitted by the user and the Register record is inserted to the database. The code is given below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Microsoft.AspNetCore.Mvc;
using MyAppT.Controllers;
using MyAppT.Models;
using Moq;

namespace TestingProject
{
    public class TestRegister
    {
        //…

        [Fact]
        public async Task Test_Create_POST_ValidModelState()
        {
            // Arrange
            var r = new Register()
            {
                Id = 4,
                Name = "Test Four",
                Age = 59
            };

            var mockRepo = new Mock<IRegisterRepository>();
            mockRepo.Setup(repo => repo.CreateAsync(It.IsAny<Register>()))
                .Returns(Task.CompletedTask)
                .Verifiable();
            var controller = new RegisterController(mockRepo.Object);

            // Act
            var result = await controller.Create(r);

            // Assert
            var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
            Assert.Null(redirectToActionResult.ControllerName);
            Assert.Equal("Read", redirectToActionResult.ActionName);
            mockRepo.Verify();
        }
    }
}

After creating a mock object, I did the setup of this mocked object called mockRepo, making sure it calls the CreateAsync() method and returns Task.CompletedTask.

mockRepo.Setup(repo => repo.CreateAsync(It.IsAny<Register>()))
    .Returns(Task.CompletedTask)
    .Verifiable();

I passed It.IsAny() as the parameter for the CreateAsync() method. The method set up with It.IsAny<> will match any parameter you give to the method that is here the Register type object.

I also used Verifiable() method to mark this setup as verifiable this means when mockRepo.Verify() is called then xUnit will verify this setup also.

I also created a test Register object called “r” and used it when calling the create action.

var result = await controller.Create(r);

The following 3 conditions are tested.

  • a. The action is redirecting to another action.
  • b. The redirected controller is null.
  • c. The redirected action method “Read”.
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Read", redirectToActionResult.ActionName);

Also recall that the mockRepo.Verify() will match the mocked object setup also, which is.


mockRepo.Setup(repo => repo.CreateAsync(It.IsAny<Register>()))
                .Returns(Task.CompletedTask)
                .Verifiable();

Unit Tests for “Read” action

There is only one unit test for Read action method. It’s code is given below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Microsoft.AspNetCore.Mvc;
using MyAppT.Controllers;
using MyAppT.Models;
using Moq;

namespace TestingProject
{
    public class TestRegister
    {
        //…

        [Fact]
        public async Task Test_Read_GET_ReturnsViewResult_WithAListOfRegistrations()
        {
            // Arrange
            var mockRepo = new Mock<IRegisterRepository>();
            mockRepo.Setup(repo => repo.ListAsync()).ReturnsAsync(GetTestRegistrations());
            var controller = new RegisterController(mockRepo.Object);

            // Act
            var result = await controller.Read();

            // Assert
            var viewResult = Assert.IsType<ViewResult>(result);
            var model = Assert.IsAssignableFrom<IEnumerable<Register>>(viewResult.ViewData.Model);
            Assert.Equal(3, model.Count());
        }

        private static List<Register> GetTestRegistrations()
        {
            var registrations = new List<Register>();
            registrations.Add(new Register()
            {
                Id = 1,
                Name = "Test One",
                Age = 45
            });
            registrations.Add(new Register()
            {
                Id = 2,
                Name = "Test Two",
                Age = 55
            });
            registrations.Add(new Register()
            {
                Id = 3,
                Name = "Test Three",
                Age = 60
            });
            return registrations;
        }
    }
}

In this test method, I setup the mock object by calling ListAsync() method of the RegisterRepository.cs class, and returning some test registrations in list way. For this I used GetTestRegistrations() method which is a static method and defined in the same TestRegister.cs class.

mockRepo.Setup(repo => repo.ListAsync()).ReturnsAsync(GetTestRegistrations());

I performed 3 test case:

a. The returned object is of ViewResult type.

var viewResult = Assert.IsType<ViewResult>(result);

b. The retuned model is of IEnumerable type. Recall the action method is returning a list of registrations.

var model = Assert.IsAssignableFrom<IEnumerable<Register>>(viewResult.ViewData.Model);

c. Tested the total count of the records in the model should be 3.

Assert.Equal(3, model.Count()); 

Note that the GetTestRegistrations() method returns 3 test records when called during the mock setup.

Unit Tests for “Update” actions

There will be 3 test method created for the Update actions. Let us add them one by one.

1. Test_Update_GET_ReturnsViewResult_WithSingleRegistration for testing HTTP GET Update action
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Microsoft.AspNetCore.Mvc;
using MyAppT.Controllers;
using MyAppT.Models;
using Moq;

namespace TestingProject
{
    public class TestRegister
    {
        //…

        [Fact]
        public async Task Test_Update_GET_ReturnsViewResult_WithSingleRegistration()
        {
            // Arrange
            int testId = 2;
            string testName = "test name";
            int testAge = 60;

            var mockRepo = new Mock<IRegisterRepository>();
            mockRepo.Setup(repo => repo.GetByIdAsync(testId)).ReturnsAsync(GetTestRegisterRecord());
            var controller = new RegisterController(mockRepo.Object);

            // Act
            var result = await controller.Update(testId);

            // Assert
            var viewResult = Assert.IsType<ViewResult>(result);
            var model = Assert.IsAssignableFrom<Register>(viewResult.ViewData.Model);
            Assert.Equal(testId, model.Id);
            Assert.Equal(testName, model.Name);
            Assert.Equal(testAge, model.Age);
        }

        private Register GetTestRegisterRecord()
        {
            var r = new Register()
            {
                Id = 2,
                Name = "test name",
                Age = 60
            };
            return r;
        }
    }
}

Note – I also have added GetTestRegisterRecord() method whose task is to return a single Register record.

The setup of mock object is done by calling the GetByIdAsync() method of RegisterRepository.cs and passing “testId” variable to it as a parameter. The return type is a single Register record which is returned by the GetTestRegisterRecord() method.

mockRepo.Setup(repo => repo.GetByIdAsync(testId)).ReturnsAsync(GetTestRegisterRecord());

Next, I did the following tests:

a. The return type should be ViewResult.

var viewResult = Assert.IsType<ViewResult>(result);

b. The model should be of Register type.

var model = Assert.IsAssignableFrom<Register>(viewResult.ViewData.Model);

c. The value of the fields (Id, Name and Age) of the model should be same as that of the record which is requested. This also makes sure that GetTestRegisterRecord method should work correctly.

Assert.Equal(testId, model.Id);
Assert.Equal(testName, model.Name);
Assert.Equal(testAge, model.Age);
2. Test_Update_POST_ReturnsViewResult_InValidModelState for testing HTTP POST Update action

This test method verifies the case when the ModelState is invalid. This happens when user tries submitting the update form without filling all the required fields. Its code is given below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Microsoft.AspNetCore.Mvc;
using MyAppT.Controllers;
using MyAppT.Models;
using Moq;

namespace TestingProject
{
    public class TestRegister
    {
        //…

        [Fact]
        public async Task Test_Update_POST_ReturnsViewResult_InValidModelState()
        {
            // Arrange
            int testId = 2;
            Register r = GetTestRegisterRecord();

            var mockRepo = new Mock<IRegisterRepository>();
            mockRepo.Setup(repo => repo.GetByIdAsync(testId)).ReturnsAsync(GetTestRegisterRecord());

            var controller = new RegisterController(mockRepo.Object);
            controller.ModelState.AddModelError("Name", "Name is required");

            // Act
            var result = await controller.Update(r);

            // Assert
            var viewResult = Assert.IsType<ViewResult>(result);
            var model = Assert.IsAssignableFrom<Register>(viewResult.ViewData.Model);
            Assert.Equal(testId, model.Id);
        }
    }
}

The setup of mock object is done by calling the GetByIdAsync() method of RegisterRepository.cs and passing “testId” variable to it as a parameter. The return type is a single Register record returned from the GetTestRegisterRecord() method.

mockRepo.Setup(repo => repo.GetByIdAsync(testId)).ReturnsAsync(GetTestRegisterRecord());

Next, I made the ModelState invalid:

controller.ModelState.AddModelError("Name", "Name is required");

Finally, I did the tests for ViewResult return type, model as a Register object and Id of the records to be same as the requested register id.

var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<Register>(viewResult.ViewData.Model);
Assert.Equal(testId, model.Id);
3. Test_Update_POST_ReturnsViewResult_ValidModelState for testing HTTP POST Update action

This test method comes for the scenario when the record is successfully updated. So add this method, the code is given below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Microsoft.AspNetCore.Mvc;
using MyAppT.Controllers;
using MyAppT.Models;
using Moq;

namespace TestingProject
{
    public class TestRegister
    {
        //…

        [Fact]
        public async Task Test_Update_POST_ReturnsViewResult_ValidModelState()
        {
            // Arrange
            int testId = 2;
            var r = new Register()
            {
                Id = 2,
                Name = "Test Two",
                Age = 55
            };
            var mockRepo = new Mock<IRegisterRepository>();
            mockRepo.Setup(repo => repo.GetByIdAsync(testId)).ReturnsAsync(GetTestRegisterRecord());
            var controller = new RegisterController(mockRepo.Object);

            mockRepo.Setup(repo => repo.UpdateAsync(It.IsAny<Register>()))
                   .Returns(Task.CompletedTask)
                   .Verifiable();

            // Act
            var result = await controller.Update(r);

            // Assert
            var viewResult = Assert.IsType<ViewResult>(result);
            var model = Assert.IsAssignableFrom<Register>(viewResult.ViewData.Model);
            Assert.Equal(testId, model.Id);
            Assert.Equal(r.Name, model.Name);
            Assert.Equal(r.Age, model.Age);

            mockRepo.Verify();
        }

    }
}

I created mock object 2 times. In the first time, I mocked it for a single Register type record.

mockRepo.Setup(repo => repo.GetByIdAsync(testId)).ReturnsAsync(GetTestRegisterRecord());

Then on the second time, I called UpdateAsync() method.

mockRepo.Setup(repo => repo.UpdateAsync(It.IsAny<Register>()))
       .Returns(Task.CompletedTask)
       .Verifiable();

Finally, I test for the following things:

a. The return type should be ViewResult.

var viewResult = Assert.IsType<ViewResult>(result);

b. The model should be Register type.

var model = Assert.IsAssignableFrom<Register>(viewResult.ViewData.Model);

c. The model id should be same as the requested one.

Assert.Equal(testId, model.Id);

d. The model’s name and age fields should be updated.

Assert.Equal(r.Name, model.Name);
Assert.Equal(r.Age, model.Age);

Unit Tests for “Delete” action

This test method tests the Delete action method. So, add the Test_Delete_POST_ReturnsViewResult_InValidModelState whose code is given below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Microsoft.AspNetCore.Mvc;
using MyAppT.Controllers;
using MyAppT.Models;
using Moq;

namespace TestingProject
{
    public class TestRegister
    {
        //…

        [Fact]
        public async Task Test_Delete_POST_ReturnsViewResult_InValidModelState()
        {
            // Arrange
            int testId = 2;

            var mockRepo = new Mock<IRegisterRepository>();
            mockRepo.Setup(repo => repo.DeleteAsync(It.IsAny<int>()))
                   .Returns(Task.CompletedTask)
                   .Verifiable();

            var controller = new RegisterController(mockRepo.Object);

            // Act
            var result = await controller.Delete(testId);

            // Assert
            var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
            Assert.Null(redirectToActionResult.ControllerName);
            Assert.Equal("Read", redirectToActionResult.ActionName);
            mockRepo.Verify();
        }
    }
}

The setup of the mock object is done by calling the DeleteAsync method and passing any int type value to it.

mockRepo.Setup(repo => repo.DeleteAsync(It.IsAny<int>()))
       .Returns(Task.CompletedTask)
       .Verifiable();

Then I test for return type to be RedirectToActionResult, controller name to be null and redirected action name to be “Read”.

var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Read", redirectToActionResult.ActionName);

Now all the tests methods are added it’s time to run all the tests in the Test Explorer. They all passed, I have shown the test result screenshot below.

controller tests pass
Conclusion

In this tutorial I explained how to test controllers of ASP.NET Core MVC with Moq and xUnit. I also created close to 8 test methods to show how different types of tests are conducted. I hope you enjoyed this tutorial so kindly share it. Do read my next tutorial also.

SHARE THIS ARTICLE

  • linkedin
  • reddit

ABOUT THE AUTHOR

I am Yogi S. I write DOT NET artciles on my sites hosting.work and yogihosting.com. You can connect with me on Twitter. I hope my articles are helping you in some way or the other, if you like my articles consider buying me a coffee - Buy Me A Coffee

Leave a Reply

Your email address will not be published.

Related Posts based on your interest