In this article, we are going to cover Unit Testing or rather why we need unit tests and how to implement them in our ASP.NET Core applications.

If you’ve missed some of the previous articles in the series we recommend visiting the series page: ASP.NET Core MVC Series.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

To download this article’s source code visit: Unit Testing in ASP.NET Core MVC.

Let’s start.

Why do We Need Unit Tests

Our applications can fail in unexpected ways in response to changes. Hence, automatic testing is required after making changes for all applications.

Manual testing is the slowest, least reliable, most expensive way to test an application. But if applications aren’t designed to be testable, manual testing might be the only way available to us.

So the first step is to make sure that the application is designed to be testable. Applications that follow good architectural principles like Separation of Concerns, Dependency Inversion, Single Responsibility, Don’t Repeat Yourself (DRY), etc are easily testable. ASP.NET Core supports automated unit, integration, and functional testing.

We have explained Unit testing in ASP.NET Core Web API in detail in one of our previous articles. There, we have explained how to implement unit testing in an ASP.NET Core Web API project. However, in this article, we’ll focus on how to write unit tests for an ASP.NET Core MVC project.

A unit test is meant to test a single part of our application’s logic. Our code may have dependencies on lower layers and external components. But these are not validated by unit tests. It runs completely in memory and in process. It doesn’t communicate with the file system, the network, or a database.

Unit tests should only test our code.

Due to these reasons, unit tests should execute extremely quickly and we should be able to run them frequently. Ideally, we should run unit tests before each change is pushed to the source control repository and also with every automated build on the build server.

Setting Up the Unit Testing Framework

There are many test frameworks available in the market today. However, for this article, we are going to use the xUnit, which is a very popular testing framework. Even the ASP.NET Core and EF Core tests are written using it.

We can add a xUnit test project in Visual Studio using the xUnit Test Project template that’s available in the visual studio:

add new unit test project

We should always name our tests in a consistent fashion, with names that indicate what each test does. One good approach is to name test classes and methods according to the class and method which they are testing. It makes it extremely clear what each test is responsible for.

We can add the behavior that is being tested to each test method name. This should include the expected behavior and any inputs or assumptions that should yield this behavior.

Thus, when one or more tests fail, it’s obvious from their names what cases have failed. We will follow this naming convention when we create unit tests in the next section.

Let’s add a NuGet package reference for Moq, which is a mock object framework. This will provide us mocked objects, which are fabricated objects with a predetermined set of property and method behaviors used for testing.

Unit Tests in Controller Methods

Let’s say we have defined an EmployeeController with an Index() and Add() methods:

public class EmployeeController : Controller
{
    private readonly IDataRepository<Employee> _dataRepository;

    public EmployeeController(IDataRepository<Employee> dataRepository)
    {
        _dataRepository = dataRepository;
    }

    public IActionResult Index()
    {
        IEnumerable<Employee> employees = _dataRepository.GetAll();
        return View(employees);
    }

    [HttpPost]
    public IActionResult Add(Employee employee)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        _dataRepository.Add(employee);
        return RedirectToAction(actionName: nameof(Index));
    }
}

Now let’s look at how to write unit tests for these action methods. Our controller uses Dependency Injection to get the value for _dataRepository. This makes it possible for the unit testing framework to supply a mock object and test the methods.

The AAA (Arrange, Act, Assert) pattern is a common way of writing unit tests and we will follow the same pattern here.

Arrange section of a unit test method initializes objects and sets the value for the inputs that are passed to the method under test.

The Act section invokes the method under test with the arranged parameters.

Assert section verifies that the action of the method under test behaves as expected.

Testing the Index() method

Now let’s write tests for the Index() method:

[Fact]
public void Index_ReturnsAViewResult_WithAListOfEmployees()
{
    // Arrange
    var mockRepo = new Mock<IDataRepository<Employee>>();
    mockRepo.Setup(repo => repo.GetAll())
        .Returns(GetTestEmployees());
    var controller = new EmployeeController(mockRepo.Object);

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

    // Assert
    var viewResult = Assert.IsType<ViewResult>(result);
    var model = Assert.IsAssignableFrom<List<Employee>>(
        viewResult.ViewData.Model);
    Assert.Equal(2, model.Count());
}

private IEnumerable<Employee> GetTestEmployees()
{
    return new List<Employee>()
    {
        new Employee()
        {
            Id = 1,
            Name = "John"
        },
        new Employee()
        {
            Id = 2,
            Name = "Doe"
        }
    };
}

We first mock the IDataRepository<Employee> service using the GetTestEmployees() method. GetTestEmployees() creates and returns a list of two mock Employee objects.

Then the Index() method is executed and we assert the following on the result:

  1. It returns a ViewResult.
  2. The type of returned model is List<Employee>.
  3. There are two Employee objects in the returned Model.

Each test method is decorated by a [Fact] attribute which indicates that it is a fact method that should be run by the test runner.

Testing the Add() method

Now, let’s write tests for the Add() method:

[Fact]
public void Add_ReturnsBadRequestResult_WhenModelStateIsInvalid()
{
    // Arrange
    var mockRepo = new Mock<IDataRepository<Employee>>();

    var controller = new EmployeeController(mockRepo.Object);
    controller.ModelState.AddModelError("Name", "Required");
    var newEmployee = GetTestEmployee();

    // Act
    var result = controller.Add(newEmployee);

    // Assert
    var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
    Assert.IsType<SerializableError>(badRequestResult.Value);
}

[Fact]
public void Add_AddsEmployeeAndReturnsARedirect_WhenModelStateIsValid()
{
    // Arrange
    var mockRepo = new Mock<IDataRepository<Employee>>();
    mockRepo.Setup(repo => repo.Add(It.IsAny<Employee>()))
        .Verifiable();
    var controller = new EmployeeController(mockRepo.Object);
    var newEmployee = GetTestEmployee();

    // Act
    var result = controller.Add(newEmployee);

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

private Employee GetTestEmployee()
{
    return new Employee()
    {
        Id = 1,
        Name = "John"
    };
}

The first test verifies that when ModelState.IsValid is false, the action method returns a 400 Bad Request ViewResult with the appropriate data. We can test for an invalid model state by adding errors using AddModelError() method.

The second test verifies that when ModelState.IsValid is true, the Add() method on the repository is called and a RedirectToActionResult is returned with the correct arguments.

Mocked methods that we don’t call are normally ignored, but by calling Verifiable() along with the setup we can validate that the mocked methods are called. We can validate this using mockRepo.Verify(), which fails the test if the expected method wasn’t called.

Running the Tests

We can run the tests using the Test Explorer in Visual Studio:

test explorer - Unit Tests

In the Test Explorer, we can see all the available tests grouped by Solution, Project, Class, etc. We can select and execute the tests by either running or debugging those. Once we run the tests, we can see either a green tick mark or a red cross mark towards the left side of the test method name indicating success or failure on the last run. Towards the right, we can see the time taken to execute each test.

That’s it. Let’s summarize what we’ve learned so far.

Conclusion

In this article we have learned the following topics:

  • Why we need unit tests in our ASP.NET Core MVC app
  • How to set it up
  • How to test the controller methods 

In the next part of this series, we are going to learn about filters in ASP.NET Core MVC.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!