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.
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:
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:
- It returns a
ViewResult
. - The type of returned model is
List<Employee>
. - There are two
Employee
objects in the returnedModel
.
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:
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.