In the previous article, we learned how to write Unit Tests by using the xUnit and the different attributes that xUnit provides for us. We’ve also seen how to test validation rules inside a single validation class.

But what about controllers and all the actions inside? Can we write tests for them too?

In this article, we are going to talk more about testing controllers in ASP.NET Core applications by using Moq and unit tests.

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 the source code for this article, you can visit our GitHub repository.

For the complete navigation of this series, you can visit ASP.NET Core Testing.

So, let’s get going.

Unit Testing Controllers Using Moq Library

Before we start, let’s take a look at the EmployeesController’s constructor code:

Repository dependency inside controller

As you can see, we are using Dependency Injection to inject the interface into our controller. So basically, our controller has a dependency on the repository logic through that injected interface.

And there is no problem with that approach at all, it is even recommended. But when we write tests for our controller or any other class in a project, we should isolate those dependencies.

There are several advantages to isolating dependencies in a test code:

  • We don’t have to initialize all dependencies to return correct values, thus making our test code much simplified
  • If our test fails and we didn’t isolate dependency, we can’t be sure whether it fails due to some error in a controller or in that dependency
  • When dependent code communicates with a real database, as our repository does, the test code could take more time to execute. This can happen due to connection issues or simply due to the time needed to fetch the data from the database.

Of course, there are additional reasons to isolate dependencies in test code, but you get the point.

That said, let’s install the Moq library in the EmployeesApp.Tests project:

Install-Package Moq

After the installation completes, we are going to create a new Controller folder in the same project and add EmployeesControllerTests class.

Creating a Mock Object

Let’s modify the EmployeesControllerTests class:

public class EmployeesControllerTests
{
    private readonly Mock<IEmployeeRepository> _mockRepo;
    private readonly EmployeesController _controller;

    public EmployeesControllerTests()
    {
        _mockRepo = new Mock<IEmployeeRepository>();
        _controller = new EmployeesController(_mockRepo.Object);
    }
}

We create a mock object of type IEmployeeRepository inside the constructor, and since we want to test the controller logic, we create an instance of that controller with the mocked object as a required parameter.

And there you go. Everything is prepared, and a dependency is mocked, so all we have to do is to write some tests.

Unit Testing the Index Action

If we take a look at the Index action in the EmployeesController class, we can see that we fetch all employees from the database and return a view with those employees:

public IActionResult Index()
{
    var employees = _repo.GetAll();
    return View(employees);
}

As a result, we can write a couple of tests to verify that this action is doing exactly what it is supposed to do. Also, you will see that testing controllers with unit tests is not that hard at all.

In the first test, we are going to verify that the Index action returns a result of type ViewResult:

[Fact]
public void Index_ActionExecutes_ReturnsViewForIndex()
{
    var result = _controller.Index();
    Assert.IsType<ViewResult>(result);
}

So, we are executing the Index action from our controller and accepting the result inside the result variable. After that, we check the type of the returned result with the IsType method. If the result is of the ViewResult type the test will pass, otherwise, it will fail.

In this test method, we didn’t use a mocked object because we didn’t use any of our repository methods. We have just checked the type of our result.

Let’s run the Test Explorer window and find out the result:

Testing Controllers - Index of type ViewResult test passes

We can see that our test passes and that the result is of ViewResult type.

Now, let’s continue by writing another test method to verify that our Index action returns an exact number of employees:

[Fact]
public void Index_ActionExecutes_ReturnsExactNumberOfEmployees()
{
    _mockRepo.Setup(repo => repo.GetAll())
        .Returns(new List<Employee>() { new Employee(), new Employee() });

    var result = _controller.Index();

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

In this test method, we are fetching the data from the database by using the GetAll repository method. Of course, we don’t want to use the concrete repository but the mocked one and therefore we use the Setup method to specify a setup for the GetAll method. Additionally, we have to use the Returns method to specify the value to return from the mocked GetAll method.

After we store the result of the Index action, we check the type of that result, the type of the model object inside that result, and finally the number of employees by using the Equal method. All three verifications have to pass in order for the test to pass, otherwise, the test will fail.

Once we run the test, we can see it passes and we verify that the Index action returns exactly two employees.

Testing Create Actions

We have two Create actions in our EmployeesController class, the GET, and the POST action. The first action just loads the Create View and that is something we have to test.

So, let’s do it:

[Fact] 
public void Create_ActionExecutes_ReturnsViewForCreate() 
{ 
    var result = _controller.Create(); 
            
    Assert.IsType<ViewResult>(result); 
}

We already had a test like this, just with the Index action, so, there is nothing new about it. If we run Test Explorer we can verify that the test passes.

Let’s move on to the second Create action, the POST one. In that action, we have a model validation and if it is invalid we return a view with the employee object.

So let’s test that:

[Fact] 
public void Create_InvalidModelState_ReturnsView() 
{ 
    _controller.ModelState.AddModelError("Name", "Name is required"); 
            
    var employee = new Employee { Age = 25, AccountNumber = "255-8547963214-41" }; 
            
    var result = _controller.Create(employee); 
            
    var viewResult = Assert.IsType<ViewResult>(result); 
    var testEmployee = Assert.IsType<Employee>(viewResult.Model); 
            
    Assert.Equal(employee.AccountNumber, testEmployee.AccountNumber); 
    Assert.Equal(employee.Age, testEmployee.Age); 
}

We have to add a model error to the ModelState property in order to test an invalid model state.

After that, we create a new employee without the Name property, which makes it invalid as well.

Finally, we call the create action and execute a couple of assertions.

With assert statements, we verify that the result is of type ViewResult and that the model is of the Employee type. Additionally, we are making sure that we get the same employee back by comparing property values from the testEmployee and the employee objects:

Testing Controllers - Create Action with invalid model state that returns a view

Additional Invalid Model Test

Let’s write one additional test to verify that the CreateEmployee method, from our repository, never executes if the model state is invalid:

[Fact] 
public void Create_InvalidModelState_CreateEmployeeNeverExecutes() 
{ 
    _controller.ModelState.AddModelError("Name", "Name is required"); 
            
    var employee = new Employee { Age = 34 }; 
            
    _controller.Create(employee); 
            
    _mockRepo.Verify(x => x.CreateEmployee(It.IsAny<Employee>()), Times.Never); 
}

The first three lines of code are the same as in the previous test method, we add a model error, create an invalid employee object, and call the Create action from our controller.

Of course, in that action, we have the CreateEmployee method which shouldn’t be executed if the model is invalid. That is exactly what we verify with the Verify method from the mocked object. By using the It.IsAny<Employee> expression, we state that it doesn’t matter which employee is passed as a parameter to the CreateEmployee method. The only important thing is that the parameter is of the Employee type.

The last parameter of the Verify method is the number of times our method executes. We use Times.Never because we don’t want to execute the CreateEmployee method at all if the model state is invalid.

You can run the test to see that it passes.

If we modify the test code by placing the Times.Once instead of Times.Never the test will fail for sure. The message that you get explains pretty well what is the problem and why the test fails.

Testing if Model is Valid in the Create Action

If the Model State is valid the CreateEmployee method should be executed just once:

[Fact]
public void Create_ModelStateValid_CreateEmployeeCalledOnce()
{
    Employee? emp = null;

    _mockRepo.Setup(r => r.CreateEmployee(It.IsAny<Employee>()))
        .Callback<Employee>(x => emp = x);

    var employee = new Employee
    {
        Name = "Test Employee",
        Age = 32,
        AccountNumber = "123-5435789603-21"
    };

    _controller.Create(employee);
    _mockRepo.Verify(x => x.CreateEmployee(It.IsAny<Employee>()), Times.Once);

    Assert.Equal(emp.Name, employee.Name);
    Assert.Equal(emp.Age, employee.Age);
    Assert.Equal(emp.AccountNumber, employee.AccountNumber);
}

So, we set up the CreateEmployee method with any employee and use the Callback method to populate the emp object with the values from the employee parameter. After that, we create a local employee object, execute the Create action with that employee and Verify if the CreateEmployee method has been executed just once.

Additionally, we verify that the emp object is the same as the employee object provided to the Create action.

Finally, let’s run that in the Test Explorer:

Create action Valid Model State CreateEmployee called once

One more test to go:

[Fact]
public void Create_ActionExecuted_RedirectsToIndexAction()
{
    var employee = new Employee
    {
        Name = "Test Employee",
        Age = 45,
        AccountNumber = "123-4356874310-43"
    };

    var result = _controller.Create(employee);

    var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);

    Assert.Equal("Index", redirectToActionResult.ActionName);
}

If we take a look at the Create action in our controller, we are going to see that after creating a new employee, we redirect a user to the Index action.

Well, that is exactly what we’ve successfully tested in our test method.

Conclusion

That is it. As you can see, testing controllers can be quite an easy job when you have the right tool to do that. We have used the Index and Create actions to show you how to test our controller and actions inside it, but all the rules can be applied to other types of Actions (PUT, DELETE…).

So, to sum up, we have learned:

  • How to use the Moq library to isolate dependency in the test code
  • To write different tests for our actions inside a controller

In the next part, we are going to talk about integration tests and how to create an in-memory database.

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