Unit testing along with Test Driven Development (TDD) have become increasingly common in recent years throughout the software development community, and MVC is no exception to this trend. With MVC a good portion of our application logic is likely to reside right inside our controllers, which makes them a great candidate for unit testing.
While every application is different, a large number of them will likely have controllers that use an underlying EntityFramework
DbContextin order to provide CRUD actions against a backing database. Having a small set of unit tests on these controller actions will help us be more confident that our applications are more robust: they will be less likely to break from unintended changes. Furthermore, in my experience, unit tests act as a living documentation for other developers contributing to the project to see how the application was intended to behave.
It almost goes without saying that unit testing our controllers is just the beginning of what we should be testing. Most developers would argue that as much logic as possible should be unit tested: unit tests are quick to write and easy to run.
While I agree with this, my real life experience tells me that it is important to start somewhere, make good progress, then build upon that momentum later. If you haven’t been keenly testing your MVC applications yet, start here to learn the basics then start today to write at least one unit test.
This post will start with setting up xUnit in our ASP.NET 5 MVC6 web application project. We’ll write a few small unit tests that use the new EntityFramework 7 in-memory store so we can test our controller actions without using mocked repositories or having to use a real test database. Finally I’ll show you how to run these tests in Visual Studio as well as on the command line using
dnx.
You can download the demo project from my GitHub here.
> git clone https://github.com/ashimoon/dotnetliberty-aspnet5-unit-testing-demo.git
xUnit
From the xUnit website:
xUnit.net is a free, open source, community-focused unit testing tool for the .NET Framework. Written by the original inventor of NUnit v2, xUnit.net is the latest technology for unit testing C#, F#, VB.NET and other .NET languages.
xUnit is becoming quite popular and is fully compatible with ASP.NET 5 / vNext / CoreCLR. This means we can easily use xUnit in our MVC6 project to start adding unit tests. I’ve had to utilize multiple resources to get xUnit set up correctly, and I’ll share all those steps here so it can be of help to others. Once set up, it has been quite nice to work with.
Setting up our project
I’m going to assume that you’re running beta8 of ASP.NET 5. If not, you can get it here (assuming you’re running Windows). Keep in mind that you will need to download both WebToolsExtensionsVS14.msi in addition to DotNetVersionManager-<flavor>.msi.
Add Dependencies
We’re going to add our xUnit dependencies to the
project.jsonfile. First, we’ll add a dependency on the main xUnit library by adding the following line to the dependencies section:
"xunit": "2.1.0"
Next, we want to add another dependency to allow the Microsoft .NET execution environment (dnx) to run our tests:
"xunit.runner.dnx": "2.1.0-beta6-build191"
In addition to our xUnit dependencies, we’re going to add a dependency on
EntityFramework.InMemory, this will allow us to create an in-memory store for our
DbContextso that we can use it in our unit tests (and avoid having to talk to a real database).
"EntityFramework.InMemory": "7.0.0-beta8"
Finally, let’s we have to add a new
"test"target in our command section. This is required for both running our tests from the command line using
dnxas well as running our tests inside of Visual Studio using the Test Explorer.
"commands": { "web": "Microsoft.AspNet.Server.Kestrel", "ef": "EntityFramework.Commands", "test": "xunit.runner.dnx" },
In the end, your
project.jsonfile should look this:
{ "webroot": "wwwroot", "userSecretsId": "aspnet5-UnitTesting-3ab808b8-45e0-493f-9c4e-15af271dba21", "version": "1.0.0-*", "dependencies": { "EntityFramework.Commands": "7.0.0-beta8", "EntityFramework.SqlServer": "7.0.0-beta8", "EntityFramework.InMemory": "7.0.0-beta8", "Microsoft.AspNet.Authentication.Cookies": "1.0.0-beta8", "Microsoft.AspNet.Authentication.Facebook": "1.0.0-beta8", "Microsoft.AspNet.Authentication.Google": "1.0.0-beta8", "Microsoft.AspNet.Authentication.MicrosoftAccount": "1.0.0-beta8", "Microsoft.AspNet.Authentication.Twitter": "1.0.0-beta8", "Microsoft.AspNet.Diagnostics": "1.0.0-beta8", "Microsoft.AspNet.Diagnostics.Entity": "7.0.0-beta8", "Microsoft.AspNet.Identity.EntityFramework": "3.0.0-beta8", "Microsoft.AspNet.IISPlatformHandler": "1.0.0-beta8", "Microsoft.AspNet.Mvc": "6.0.0-beta8", "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-beta8", "Microsoft.AspNet.Server.Kestrel": "1.0.0-beta8", "Microsoft.AspNet.StaticFiles": "1.0.0-beta8", "Microsoft.AspNet.Tooling.Razor": "1.0.0-beta8", "Microsoft.Framework.Configuration.Abstractions": "1.0.0-beta8", "Microsoft.Framework.Configuration.Json": "1.0.0-beta8", "Microsoft.Framework.Configuration.UserSecrets": "1.0.0-beta8", "Microsoft.Framework.Logging": "1.0.0-beta8", "Microsoft.Framework.Logging.Console": "1.0.0-beta8", "Microsoft.Framework.Logging.Debug": "1.0.0-beta8", "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-beta8", "xunit": "2.1.0", "xunit.runner.dnx": "2.1.0-beta6-build191" }, "commands": { "web": "Microsoft.AspNet.Server.Kestrel", "ef": "EntityFramework.Commands", "test": "xunit.runner.dnx" }, "frameworks": { "dnx451": { }, "dnxcore50": { } }, "exclude": [ "wwwroot", "node_modules" ], "publishExclude": [ "**.user", "**.vspscc" ], "scripts": { "prepublish": [ "npm install", "bower install", "gulp clean", "gulp min" ] } }
MyDbContext
For this demo, we’ll use a pretty simple EntityFramework
DbContext. It will have just one set of
Personcalled
People
People.
using Microsoft.Data.Entity; using Microsoft.Data.Entity.Infrastructure; using System; using System.ComponentModel.DataAnnotations; namespace UnitTesting.Data { public class MyDbContext : DbContext { public MyDbContext(DbContextOptions options) : base(options) { } public DbSet<Person> People { get; set; } } public class Person { [Key] public Guid Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } }
Register MyDbContext as a Service
We’re going to inject
MyDbContextinto our controller using a new feature included with ASP.NET 5 called dependency injection. If you haven’t already read my post on dependency injection, you can check it out here. This is going to allow us to declare
MyDbContextas a constructor parameter on our controller rather than creating it inside the controller. This is important because it will allow us to easily swap it out with an in-memory version for unit testing – we can run our unit tests and validate that our database was updated correctly without requiring a physical database.
Let’s go to
Startup.csand jump down to the
ConfigureServicesmethod. In this example, for simplicity sake I’m using an in-memory database rather than using SQL Server. In your application, this would most likely be using some sort of connection string to talk to your SQL server (or whatever database you’re using).
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // Add Entity Framework services to the services container. services.AddEntityFramework() .AddSqlServer() .AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])) .AddDbContext<MyDbContext>(options => options.UseInMemoryDatabase()); // ...
Add Actions to HomeController
Now let’s add a couple of actions to our
HomeControllerthat use our
MyDbContext.
using System; using System.Linq; using Microsoft.AspNet.Mvc; using UnitTesting.Data; namespace UnitTesting.Controllers { public class HomeController : Controller { private MyDbContext _context; public HomeController(MyDbContext context) { _context = context; } public IActionResult GetPerson(string name) { Person person = _context.People.FirstOrDefault(x => x.FirstName == name); return View(person); } [HttpPost] public IActionResult AddPerson(Person person) { // Let db assign id person.Id = Guid.Empty; _context.Add(person); _context.SaveChanges(); return Ok(); } public IActionResult Index() { return View(); } // ...
Adding Our Unit Tests
I like to keep my unit tests in a folder called
Tests. Let’s create a folder in the root of our project called
Tests, and another folder in there called
Controllers. Finally create a class called
HomeControllerTestinside of
Tests/Controllers.
Test Setup
The first thing we need to do is some setup work inside our test constructor. We’ll use this in order to create an in-memory
MyDbContextand we’ll inject that into a
HomeControllerinstance that we’re going to make use of in our unit tests.
using Microsoft.AspNet.Mvc; using Microsoft.Data.Entity; using System; using System.Linq; using UnitTesting.Controllers; using UnitTesting.Data; using Xunit; namespace UnitTesting.Tests.Controllers { public class HomeControllerTest { private MyDbContext _context; private HomeController _controller; public HomeControllerTest() { // Initialize DbContext in memory var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseInMemoryDatabase(); _context = new MyDbContext(optionsBuilder.Options); // Seed data _context.People.Add(new Person() { FirstName = "John", LastName = "Doe" }); _context.People.Add(new Person() { FirstName = "Sally", LastName = "Doe" }); _context.SaveChanges(); // Create test subject _controller = new HomeController(_context); } } }
Now we’ll have an instance of
HomeController(
_controller) that is using an in-memory copy of
MyDbContext(
_context) with a bit of data seeded ahead of time. Our unit tests can make all the changes it wants to this
DbContextand we can validate the state of it after the test completes.
Our First Unit Test
Now all we have to do is add a new method to
HomeControllerTestsand decorate it with a
[Fact]attribute (this is what tells xUnit that this method is a unit test). Let’s write a simple test to validate that our
GetPersonaction returns John when we ask for it.
[Fact] public void Get_person_john_returns_john() { // Act var result = _controller.GetPerson("John") as ViewResult; // Assert Assert.IsType(typeof(Person), result.ViewData.Model); Person model = (Person)result.ViewData.Model; Assert.Equal("John", model.FirstName); Assert.Equal("Doe", model.LastName); }
This test is pretty simple: it invokes the
GetPersonaction on our controller and validates that the result contains the
Personinstance we expect (which was seeded into our in-memory
MyDbContextin the test setup constructor).
Running it
There are two ways we can run our unit test. The first way is through Visual Studio, the second way is through the command line using
dnx. Let’s start with Visual Studio.
From the
Testmenu, select
Windows->
Test Explorer. This will bring up a panel that should show all the unit tests in our project. If you don’t see the unit test we added above, you probably need to build the project in order to have the test discovered. (Click
Build->
Build Solution).
Now just press
Run Allin the
Test Explorer. It should run through our unit test and light up green to let us know that it passed.
Alternatively, fire up a console, navigate to the project root, and type:
> dnx test
xUnit.net DNX Runner (32-bit DNX 4.5.1) Discovering: UnitTesting Discovered: UnitTesting Starting: UnitTesting Finished: UnitTesting === TEST EXECUTION SUMMARY === UnitTesting Total: 1, Errors: 0, Failed: 0, Skipped: 0, Time: 0.218s
Adding More Test Cases
Let’s not forget to test the negative case – when the person isn’t expected to be in the database.
[Fact] public void Get_non_existent_person_returns_null() { // Act var result = _controller.GetPerson("Fred") as ViewResult; // Assert Assert.Null(result.ViewData.Model); }
And finally, let’s add another more involved test for
AddPerson.
AddPersonwill take the person we gave it and save it into
MyDbContext. For this test we’ll actually look inside of
MyDbContext(
_context) to inspect the
Peopleset to ensure our new
Personwas added correctly.
[Fact] public void Add_person_saves_to_db_with_generated_id() { // Arrange Guid personId = Guid.NewGuid(); Person person = new Person() { Id = personId, FirstName = "Billy", LastName = "McBill" }; var beforePersonCount = _context.People.Count(); // Act var result = _controller.AddPerson(person) as HttpStatusCodeResult; // Assert Assert.Equal(200, result.StatusCode); Person savedPerson = _context.People.Single(x => x.FirstName == "Billy" && x.LastName == "McBill"); Assert.NotEqual(personId, savedPerson.Id); Assert.Equal(beforePersonCount + 1, _context.People.Count()); }
This test let’s us validate a few important things:
- The
Person
is added to the database - The
Id
is set by the database, the value provided by is is discarded as intended - The number of elements in the database is increased by 1 after this operation completes
Wrapping it Up
This is by no means all the things you’d want to assert, but it should give you some ideas of a few of the things we can start testing. At this point, adding more tests and extra validations/assertions is easy. I encourage you to start by adding a few tests into your own project and getting the hang of it. Perhaps you want to test every action, or perhaps you want to target the more important actions. Either way, I hope this post helps you see the value in adding unit tests to your MVC project and just how painless it is.