Software Gardening: Insecticide – Getting Started with Unit Testing using an ASP.NET MVC application

Abstract: Get started with unit testing using a simple ASP.NET MVC application. You’ll see how to setup the test and remove the database from the testing process.
Gardeners understand the problems that insects can cause to their plants. Entire gardens can be destroyed in short time. To stop insects from killing the plants, gardeners use insecticides. In fact, different types of insecticides may be needed. Some are organic. Some are chemical. Some kill off one type of insect, others kill off a different type of insect. And, of course, another name for the insect is bug.
Bugs kill our Software Garden as quickly as insects kill gardens. Our insecticide is good testing. Just as gardens use different types of insecticides, as software gardeners, we should use different types of testing. Integration, capacity, acceptance, functional, system, unit, regression, stress, and performance are just some of the testing types we can use.
Of all these types of testing, there is one in particular that is of interest to developers and is the first line of defense when considering insecticides for your software. That one test type is unit testing.
Many people compare software development to building construction. But software acts more organic than a building. It's more like a garden. This column discusses concepts, techniques, practices, and tools to help you get the most from your software gardenRead all our Software Gardening articles here
Think for a minute about your code. Do you have statements that include if, switch, for, foreach, while, do...while? Now think about traditional testing techniques where the developer writes the code, compiles it, checks it in, and then throws the executable over the wall to the QA team. They go to work, testing the running program, finding bugs, then throwing things back to the developer, who writes code, compiles it, checks it in, and throws it over the wall again. This cycle continues on and on and on.
Why does this cycle continue? One reason is the QA team has no idea where every if, for, foreach, while, etc. exist in the code. No, scratch that. They don’t know where any of those statements are in the code because they don’t see the code. How can QA possibly know how or where to test the application?
But it’s worse than you think. In his seminal book, “Testing Computer Software”, Cem Kaner talks about G.J. Myers who, in 1976 described a 100-line program that had 1018 unique paths. Three years later, Meyers described a much simpler program. It was just a loop and a few IF statements. In most languages you could write it in 20 lines of code. This program has 100 trillion paths.
Those numbers are daunting. How can you possibly test this? The short answer is, you can’t. The long answer is, Meyers had contrived examples, but they do show how complicated code can be and how important it is for the developer to write unit tests.
In this issue, I’m going to show you how to get started with unit testing using a simple ASP.NET MVC application. You’ll see how to setup the test and remove the database from the testing process. Through it all, I’ll keep it simple. I will not discuss java script testing nor will you see production ready code. I’m also not going to talk about Test Driven Development (TDD) because if you’re learning unit testing, you have enough to figure out without turning the entire development process upside down.

What exactly is Unit Testing?

Unit tests are written by the person who wrote the code. Who else knows the code better? Unit tests should be run entirely in memory and should not access databases, nor external files or services. A unit is a small piece of code; for example, a method. The method should be kept small and should do one thing and one thing only. Not only does this make the code easy to test, it makes it easy to maintain.
To write unit tests, you need a unit testing framework. Some popular ones are MSTest that comes with Visual Studio, NUnit, and XUnit. There are others. I will use NUnit in my examples. Don’t worry about downloading NUnit. We’ll use NuGet to add this to our test project.
You then need a way to run tests. Test runners execute the tests and give feedback about which tests pass and which fail. This is typically called red/green. Red tests fail. Green tests pass. You need two types of runners. The first is some type of GUI application that you use while you are creating code. The other is a console application that you can use on your Continuous Integration (CI) server. Choose the runner that supports the testing framework you have chosen. Again, MSTest is built into Visual Studio, but NUnit and XUnit have their own runners. Additionally, the Visual Studio test runner has been opened up so that it will also run NUnit and XUnit tests. There are also commercial tools, such as Resharper, that have test runners. Then there are commercial tools, such as NCrunch and Test Driven.Net, that are specially designed just for running unit tests.
I’m going to use NUnit using the Visual Studio test runner for the examples, then at the end, I’ll show you NCrunch and tell you why it’s a superior tool and how increased productivity will pay the cost of the tool.
So, which unit testing framework should you choose? If you are in a Team Foundation Server (TFS) shop, MSTest is probably your best choice. The test runner is built into Visual Studio and TFS. If you are not in a TFS shop, choose one of the others because you can’t separate the MSTest runner from Visual Studio or TFS to install on your build server. I use TeamCity from JetBrains as my Continuous Integration server. It has its own test runners for NUnit and MSTest. It also a built-in test coverage tool for NUnit and supports console application test runners for other unit test frameworks.

Visual Studio Project Setup

Before creating our project, let’s enable NUnit. We’ll do this in two steps. First, install the NUnit Test Adapter. This will allow NUnit to integrate into Visual Studio and use the Visual Studio test runner. To install the NUnit Test Adapter, select Tools > Extensions and Updates from the Visual Studio menu. On the left-hand menu, select Online, then type NUnit into the search box. Click Install and walk through the install process. You’ll need to restart Visual Studio when you’re done.
nunit-test-adapter

We’ll handle the second part of adding NUnit after creating the project. My example application is an ASP.NET MVC 5 application. When I created it, I named the solution DncDemo and the project DncDemo.Web. I checked Add unit tests and renamed the unit test project to DncDemo.UnitTests. I did not change authentication options. I unchecked Host in the cloud.
Once Visual Studio has create the project, we can move on to the second part of NUnit setup; adding the NUnit assemblies. You can do this through the NuGet Package Manager. Be careful that you add it only to the DncDemo.UnitTests project. Add a reference to the DncDemo.Web project.
Go ahead and add a simple Model, Controller, and some Views. I have one named Customer that is for simple CRUD of the Customer table.
In a production application, I have all data access go through a Data project (in this example, it would be named DncDemo.Data) and rename the Models folder to ViewModels. I am not doing that here because I want to keep things simple.
As a best practice, MVC controllers should be very thin, meaning they should not do anything other than pass the request on to another object and then return the result back to the View. In other words, there shouldn’t be any data access or business logic in the Controller. In the DncDemo.Web project, add a new folder named Services. The controller will use classes in this folder to handle everything.

Your first unit test for an ASP.NET MVC 5 application

Now, let’s create the first unit test. We’ll start with something simple, the Index method of the Customer class. The first thing to do is think of something to test. Let’s see, we can make sure we get all the rows in the customer table. I’ll walk you through the steps.
1. The Index method of the CustomersController has some data access code. Start by refactoring it out. Create a new class named CustomerService in the Services folder. Move code from the CustomersController to the service.
public class CustomerService
{
    private Context db = new Context();
  
    public List<Customer> GetCustomersForIndex()
    {
        return db.Customers.ToList();
    }
}
2. Now update the controller to use the service. When you refactor, work slowly, working on small parts of code at a time. Eventually, every method in the controller will be refactored to call the service and you can remove the instantiation of the Context object. I’ll leave most of the work for you to do as an exercise.
private Context db = new Context();
private CustomerService service = new CustomerService();
  
public ActionResult Index()
{
    return View(service.GetCustomersForIndex());
}
3. The refactoring is done for now, so you should test the code. Since we don’t have any unit tests yet, you’ll need to run the site and navigate to the Customer Index page.
4. Now add a new class, CustomerTests to the DncDemo.UnitTests project. Add a reference to NUnit.Framework.
using DncDemo.Web.Services;
using NUnit.Framework;
  
namespace DncDemo.UnitTests
{
    [TestFixture]
    public class CustomerTests
    {
        [Test]
        public void Index_Returns_AllRows()
        {
            // Arrange
            CustomerService service = new CustomerService();
  
            // Act
            var actual = service.GetCustomersForIndex();
  
            // Assert
            Assert.AreEqual(3, actual.Count);
        }
    }
}
The TestFixture attribute tells NUnit that the class contains tests. The Test attribute tells NUnit that the method is a test.
The name of the method is important. It should be made up of three parts, what you are testing (Index), what test actually does (Returns), and what the expected result is (AllRows). A test should test for one thing and one thing only. You may have many tests that do nothing but return the results of the GetCustomersForIndex method, each testing for a different result.
Now down in the code, three things are happening. Every good unit test does these three things - Arrange, Act, and Assert. Arrange is where you do all the setup needed to prepare to run the method under test. Act is where you actually call the method. Assert is where you compare the results of the method with what you expect.
Now that the code has been refactored and the unit test added, it’s time to run the test. Here are the steps:
  1. Stop writing code
  2. On the Visual Studio menu, select Test > Run > All tests
  3. Wait for the code to compile
  4. Wait for the tests to run
  5. View the results
You can see the test results in several places. First in the code for the service.
unit-test-result
unit-test-code
Finally, in the Test Explorer window.
test-explorer
In all three places, you see a red circle with an X, indicating the test did not pass. The Test Explorer gives additional information, the time it took to run the test.
So, we know there is an error because the test failed. Can you see it? Can you see other problems? The Assert is explicitly checking that three rows were returned. How can we know there are three rows in the table? The name of the method is Index_Returns_AllRows. Yet, we are checking for three. Finally, the test won’t even run. It throws an error because it can’t even get to the database.
With all this information, we need to fix something. First, you have to figure out what’s not working. In this case, the code can’t reach the database because the app.config file for the unit test project doesn’t know about the database. Don’t add a connection string. Remember, unit tests should run entirely in memory. We need a way to 1) remove the database access and 2) know how many rows are in the “table” that we query.

Removing the database

Removing the database is easier than is sounds. We’ll do it in two steps, repositories and mocks. The cool thing is, once we’re done, the exact same code will work for unit testing or for when we actually run the web site. This is important. If we have to change code between unit testing and actually running, we could have untested code that contains bugs.

Repositories

When you look at the code, the CustomerService class instantiates the context. You might be inclined to use IContext instead of DbContext to get rid of the database. But Entity Framework didn’t have IContext until version 6 and even then, it’s not really suitable for unit testing. We’ll fall back to a well-known pattern called the Repository Pattern.
In the Models folder, create a new interface called ICustomerRepository. In this interface, you’ll define all the methods needed to access the Customer table. You should name each method something that makes sense for the context it’s used for. In the code, I’ve defined four methods even though we’ll only implement one of them in this column.
public interface ICustomerRepository
{
    IQueryable<Customer> GetAll();
    Customer GetById(int id);
    void Save(Customer customer);
    void Delete(Customer customer);
}
Now to implement the interface. Add the class CustomerRepository to the Models folder.
public class CustomerRepository : ICustomerRepository
{
   private Context _context = new Context();
 
    IQueryable<Customer> ICustomerRepository.GetAll()
    {
        return _context.Customers;
    }
  
    Customer ICustomerRepository.GetById(int id)
    {
        throw new <strong>NotImplementedException();</strong>
    }
 
    void ICustomerRepository.Save(Customer customer)
    {
        throw new <strong>NotImplementedException();</strong>
    }
  
    void ICustomerRepository.Delete(Customer customer)
    {
        throw new <strong>NotImplementedException();</strong>
    }
}
The Context class in instantiated as a field and then used to get the actual data from the database.
Finally, we need to refactor the CustomerService class to use the repository.
public class CustomerService
{
    private ICustomerRepository customerRepo = new CustomerRepository();
  
    public List<Customer> GetCustomersForIndex()
    {
        return customerRepo.GetAll().ToList();
    }
}
The unit tests won’t run yet, but you can run the web site to verify data is getting returned to the Index method. Don’t confuse running the site with running a unit test. Think about what takes less time, running the site, logging in, navigating to the page and then verifying the data or running the unit test.

Mocking the database

Next up, we need to make the test pass. To do this, we will trick the CustomerService class into thinking there really is a database. It’s actually quite easy because of the ICustomerRepository interface. To make it easier, we’ll use a mock, which is nothing more than a fancy way of faking out the CustomerService class. A mocking framework makes this easier to do. I’ll use one called Moq. Using NuGet, add Moq to the DncDemo.UnitTests project. Do not add it to the DncDemo.Web project.
Here’s the modified unit test code wired up for the mock.
using System.Linq;
using DncDemo.Web.Models;
using DncDemo.Web.Services;
using Moq;
using NUnit.Framework;
  
namespace DncDemo.UnitTests
{
    [TestFixture]
    public class CustomerTests
    {
        [Test]
        public void Index_Returns_ThreeRows()
        {
            // Arrange
            Mock<ICustomerRepository> mockRepo = new Mock<ICustomerRepository>();
            mockRepo.Setup(m => m.GetAll()).Returns(new Customer[]
                {
                    new Customer {Id = 1, FirstName = "Herman", LastName = "Munster"},
                    new Customer {Id = 2, FirstName = "Rocky", LastName = "Squirrel"},
                    new Customer {Id = 3, FirstName = "George", LastName = "Washington"}
                }.AsQueryable());
  
            CustomerService service = new CustomerService(mockRepo.Object);
  
            // Act
            var actual = service.GetCustomersForIndex();
  
            // Assert
            Assert.AreEqual(3, actual.Count);
        }
    }
}
First, I renamed the test method to Index_Returns_ThreeRows() to indicate the number of rows returned. This better describes the expected results. Next, in the Arrange section, the mock is instantiated. And then the mock is setup. What that line says is when the GetAll method is called, the return value is an array of three items that is cast to Queryable. In the Act section, note that I changed the instantiation of the CustomerService class to pass an ICustomerRepository to the constructor. So, now we need to fix up that class.
public class CustomerService
{
    private readonly ICustomerRepository customerRepo;
  
    public CustomerService()
    {
        this.customerRepo = new CustomerRepository();
    }
    public CustomerService(ICustomerRepository customerRepo)
    {
        this.customerRepo = customerRepo;
    }
    public List<Customer> GetCustomersForIndex()
    {
        return customerRepo.GetAll().ToList();
    }
}
What will happen for testing is the mocked ICustomerRepository will be passed in and used. At runtime, CustomerRepository is instantiated using the constructor with no parameters, so it will use the actual database.
Now repeat the same five steps for running the unit test. If you look at Test Explorer, you’ll see the test now passes.
test-explorer-pass
This is a common pattern when working with unit tests: Write code, write tests, run tests, see the fail, refactor, update tests, run tests. It’s a great feeling when tests pass. It gives better confidence the code is correct and you’ll have fewer bugs.

A better way to unit test: NCrunch

Go back and look at the steps for running unit tests. To summarize, you stop writing code, you wait for the project to compile, you wait for unit tests to run. I can’t stress enough the negative impact this has on productivity. Basically, you’re sitting at your desk, waiting for tests to complete. But there is a better way, NCrunch!
At first, you may balk at spending money on a commercial application, but I assure, it will quickly pay for itself due to increased productivity. This is because NCrunch compiles code in the background and displays results right in Visual Studio, line by line. No stopping the code writing process. No waiting for the code to compile. No waiting for tests to run. This is one tool every software gardener needs to have in the toolshed.
Download NCrunch from http://www.ncrunch.net then install it. Open your project and enable NCrunch for the project. From the Visual Studio menu select NCrunch > Enable NCrunch. You may have to walk through the NCrunch Configuration Wizard (an option on the NCrunch menu). When I do this, I usually pick the defaults on every page except Ignored Tests, where I generally select Let my tests run.
Once you finish the wizard, you’ll notice some black dots along the left edge (called the gutter) of the Visual Studio editor. These appear in both the actual code and the unit test code. The first time, you have to enable the test. Go to the unit test code, right click on the green circle with the line through it and select Unignore starting test. You will immediately see some of the black dots turn to green. This tells you the tests passed. Switch to the CustomerService code. The dots are there too!
ncrunch-unit-test
Yellow dots indicate the line of code that took longer to run than NCrunch thought it should. You may need to see if you can optimize that code. If the dots are red, the test doesn’t pass. A red X indicates the actual line of code that failed.
Green, red, and yellow dots also tell you something else. You immediately get a feel for code coverage. This is an important unit testing concept that tells you which lines of code have tests against them and which ones don’t. The black dots indicated untested code. To get code coverage with the Visual Studio test runner you have to stop coding, select the Code Coverage option from the VS Test menu, then wait for code to compile and tests to run.
If you now make a change to CustomerService.GetCustomersForIndex or the unit test code, NCrunch will do its work in the background and give you immediate feedback.
NCrunch has also has features such as debugging into failing tests, code metrics, support for both NUnit and MSTest frameworks, and many more. I won’t go into these features now. That’s an exercise for you.
Note that NCrunch is a Visual Studio add-in so you still need a way to run unit tests on the build server. That’s where something like TeamCity’s unit test runner, TFS, or your unit test framework’s console runner come into play.

Where to next?

One test does not complete your testing job. Try to think of other tests to run. For example, what happens if there are no rows returned? Or null? Or you can test to verify the object really is IQueryable<Customer>.
Unit tests should look at two main areas. The first, called the happy path, the code works correctly. The second, the sad path, tests what happens when things don’t go right. Does the application blow up? Is a specific error thrown (unit test frameworks let you check for this). How about bounds checking? A common thing to check for is null. It’s surprising how many applications don’t properly handle null.
One question I often get asked is, “How do I integrate this into legacy code?” There are three candidates for code that should get unit tests. First, code you are modifying. Second, code that has lots of bugs or customers complain about most. Third, areas of code that you are afraid to modify because it’s fragile and breaks every time you change it.
Don’t be afraid to tell your boss that doing unit testing will add time to write the code. But that time will be far less than writing the code, then fixing bugs that come back from the QA team. And the QA team will spend less time testing the code and more time doing QA activities that you can’t easily automate.
Work slowly, refactor to small, easily tested methods. Write the tests as you go. Don’t move onto another section until the section you’re working on passes all its tests. If you have a particularly bad section of code, you might want to set aside half or even a full day a week for refactoring and writing unit tests until you’re satisfied it’s fixed. And don’t check the code into Version Control until all unit tests pass.
There is much more to unit testing and mocks. I encourage you to explore those areas. You may also consider learning about Dependency Injection. It will eliminate the need for two constructors in the service class. Finally, once you get proficient with unit testing, you may want to look at Test Driven Development (TDD). What I’ve shown you here is Test After Development (TAD) where you write the code then write the tests for it. With TDD, you write the test first, then write code to make it pass. It not only tests the code, but tests your assumptions about how the code should be architected.

Summary

Unit tests are one of the most important tools you can use. They are your first line insecticide against bugs that can kill your application. Every application you write, should use them. By following the gardening practices here, your software will grow and be lush, green, and vibrant.

Comments