.NET Core: Creating an xUnit Test for an Application with a Generic Repository Pattern
In all honesty I found it pretty damn hard to get this working, and I was questioning whether I should have used Unit of Work in the first place (there are already layers of abstraction in the standard .NET Core template project). I managed to get a functioning xUnit test method that mocks the repositories, populates them with data and verifies the model returned by a controller method.
Setting Up a Unit Test Project
Add a xUnit project to the solution, then add an assembly reference in that to the project being tested. Just to check the test method executes and to show the format of a basic class:
using Xunit;
public class UnitTest1
{
[Fact]
public void SimpleTest()
{
int five = 5;
Assert.Equal(5, five);
}
}
When the test is run, the method will execute and show in the results as a test that passed. That's because I declared five as '5', and used Assert.Equal to check the two values match.
Test methods typically have sections for Arrange, Act and Assert - instantiating a data model, defining an expected result and comparing the actual result against it - and, of course, there are multiple Assert methods that could be used for testing various other conditions.
And, if your code is anything like mine (which deviated somewhat from the prescribed way of doing things), it will need to:
Create a test instance of IUnitOfWork and pass that to the application's controller
Create mock repositories
Associate test entities with the repositories
Check the test entities against what's returned by the controller being tested
Testing a Home Controller Method
Unit testing a HomeController in a .NET Core application that's using the repository pattern is less straightforward. I'm not using DbContext or the Entity Framework model directly. The good news is it's not necessary to duplicate the Repository Pattern or the Unit of Work, as they can be imported from the application project:
using DotNetCore6.Controllers;
using DotNetCore6.Data;
using DotNetCore6.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
The unit test must create an instance of HomeController, which requires something like IUnitOfWork to be passed to it. This takes the place of DbContext.
public HomeController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
Moq can be used for creating a test instance of IUnitOfWork and the repositories, and for passing them to HomeController. The method under test, PowerOn(), simply fetches a list of computers from the Computers model and returns that in a view.
[Fact]
public void TestPowerOnView()
{
var mockUoW = new Mock<IUnitOfWork>();
var powerOnService = new HomeController(mockUoW.Object).PowerOn();
var result = powerOnService.View();
Assert.IsType<ViewResult>(result);
}
After setting a breakpoint within the HomeController constructor and running the test in debug mode, it will be evident that something like IUnitOfWork had been passed to it, and there are references to the Entity Framework models.
Attaching Repositories Using Setup()
The next step is to attach the repositories to the Unit of Work being passed to HomeController. The Setup() method will do all the work of adding them to Mock<IUnitOfWork>() anyway.
If we use something like the following code (the GetAll() method is defined in my implementation of Unit of Work) and set a breakpoint on the HomeController again, we find that it's discovered IComputersRepository, ILabsRepository and IUsersRepository. The interfaces don't refer to actual repositories or entities yet.
[Fact]
public void TestPowerOnView()
{
var mockUoW = new Mock<IUnitOfWork>();
mockUoW.Setup(a => a.Computers.GetAll());
mockUoW.Setup(a => a.Labs.GetAll());
mockUoW.Setup(a => a.Users.GetAll());
var powerOnService = new HomeController(mockUoW.Object).PowerOn();
var result = powerOnService;
Assert.IsType<ViewResult>(result);
}
Adding Mock Entities
The next problem is that of getting the entities passed to the controller. Normally a repository will refer to an IEnumerable/List<Entities>. For the sake of making things easier to follow, I've created a mock repository within the test method.
[Fact]
public void TestPowerOnView()
{
List<Computers> mockComputer = new List<Computers>()
{
new Computers()
{
id = 1,
host = "TEST COMPUTER",
ip = "193.134.9.5",
mac = "TEST MAC ADDRESS",
subnet = "255.255.0.0",
broadcast = "255.255.255.255",
created_at = System.DateTime.Now,
updated_at = System.DateTime.Now
}
};
var mockUoW = new Mock<IUnitOfWork>();
mockUoW.Setup(a => a.Computers.GetAll()).Returns(mockComputer);
var powerOnService = new HomeController(mockUoW.Object).PowerOn();
var result = powerOnService;
Assert.IsType<ViewResult>(result);
}
The method will try GetAll() with an empty repository, but still return the entities in mockComputer. What I didn't realise is we need to put a breakpoint in the method being tested itself to see the model being passed into it.
This test will pass, but it only verifies that a view was returned by PowerOn(). It doesn't tell us anything about the data being returned.
Testing the Return Data
In this case, getting the model returned also wasn't straightforward, as I couldn't access the model directly. I needed to add some extra code to extract that.
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<Computers>>(viewResult.ViewData.Model);
Assert.Equal(mockComputer, model);