The Ultimate Guide To Unit Testing in ASP.NET MVC

With most development, you need checks-and-balances with your application. Today, I show you what makes unit testing great and the best ways to achieve exceptional unit test coverage in your ASP.NET MVC application.

Written by Jonathan "JD" Danylko • Last Updated: • MVC •
Hammer breaking fragile ground

Unit tests are definitely becoming a need-to-have skill for developers to confirm their code does what it should for production.

If you aren't building unit tests for your code, I would suggest looking into unit test libraries and frameworks along with a mocking framework to intercept external calls to other resources.

I used to be one of the developers who poo-poo'd unit tests, but now, I can't live without them. They're a way to perform checks-and-balances on your code.

Personally, there have been two instances that have changed my way of thinking with unit tests:

  • There was a large C# system that was missing a simple, but critical business rule (but it really wasn't). The manager said that they weren't going to compensate me because it wasn't coded. I opened my unit tests and ran specific unit test to show them the code and that it was working as expected. Luckily, the code was a very simple unit test with three lines and the client understood the unit test. He was convinced that I did code it and that it functioned properly. After the incident, I was promptly compensated.

  • I wrote a validation engine for a company that had an insane amount of validations. Right before we finished the validation engine, we had a large amount of unit tests backing the functionality of the engine so if something was coded wrong or something was modified in the validation engine, the unit test would catch it. At one point, we needed to refactor the code. The great part about this is that it took me two days to refactor the code...and by the end of those two days, all of the existing unit tests passed except one. The one unit test needed adjusting because of the new syntax, but after modifying the unit test, all unit tests passed successfully. All Green! If I didn't have all of those unit tests, I would've been rewriting the code blindly.

As you can see, these unit tests solve a number of issues in their own way.

Let's get into the basics of different types of testing.

Terminology

The basics of testing can be broken into the following categories:

  • Unit Testing
    This type of testing is meant to focus on one unit of code, usually a class or a method and assure the developer that the proper expected behavior is returned.

  • Integration Testing
    Integration Testing is when a program accesses an external resource to confirm that the code is functioning properly (i.e. database calls or loading a file). If you are making database calls in your unit tests, then they aren't called unit tests...they are integration tests

  • Regression Testing
    Over time, as your system grows, you accumulate more and more unit tests. Those old unit tests need to run just like the new ones. Regression testing is finding defects after a major code change has occurred. Those old unit tests may need refactored to match the new code. Of course, if it is new code, I could almost guarantee that at least 25-50% of your unit test code would fail (considering it's a major change to your code base).

  • Load Testing
    After your website is built, you may need to perform some load testing to determine the stamina of your code. What I mean is that load testing is primarily concerned with how well the system runs under a specific load of users or large amounts of data. 

With those terms explained, the unit tests and integration tests are the most important for this particular post, but trust me, there are a ton of different software testing methods!

Everyone, and I mean everyone, in the IT industry is testing the quality of their products. From the first line of code a developer writes to the last power user giving their stamp-of-approval before it heads out the door...

...Everyone is a part of QA now (in one way or another)!

What are some general guidelines for Unit Testing?

Based on my experiences, here are some of my guidelines I've learned over the years.

  • Make sure you unit tests tests what needs to be unit tested...and nothing more.
    If you have a simple POCO (Plain Old CLR Object) with a list of properties with no implementation or business rules behind it, I'm guessing you don't need to test it.

  • Your code should be unit testable. If it's not, you may need to rethink your design.
    Sometimes, it's a good idea to write your unit test first. That way, you get a clear understanding and perspective of how developers will interact with your code. If it's not clear in your unit test, what makes you think that developers will understand it in production? ;-)

  • You really don't need to mock everything.
    Some users mock the heck out of everything because they can. For example, your business objects. No mocking is necessary. You can new one up just to see if code is structurally sound and passes a specific business requirement.

  • If a business routine or rule is really complex, write unit tests around it (or take a hammer to it).
    Confirm the nature of the routine by writing unit tests. If it's really complicated and it's a long method, "break" it into manageable pieces of code. Breaking this code smell into manageable chunks of code will accomplish two things: smaller, testable routines for unit testing and a better understanding of the code since it's refactored.

  • Write small unit tests to achieve quick wins
    I've seen a number of developers write elaborate unit tests showing 50 lines of code to return back one number. Remember when I said your unit tests will show developers how complex your system is? While 50 or more lines of code in a unit test is definitely a design problem/code smell, the best unit tests focus on smaller parts of the system and achieve better granularity across the system components.  

  • Use as many Asserts to confirm the behavior works as expected
    I've heard a number of developers argue that you need only one Assert per unit test. I disagree. You may return a number of parameters in a ViewModel that HAS to have a list of records, a total, and a page title. I would not make three methods to check each one separately. I would create one unit test with three Asserts.

Of course, unit testing can be specific to individuals or even a company culture.

Does anyone feel the same way? Post your comments below. Give me your thoughts.

So How Does Unit Testing Work in ASP.NET MVC?

This was one of the selling points with ASP.NET MVC for me. The structure of an MVC project is already testable right out of the box.

Once you start Visual Studio and create your first ASP.NET MVC project, you can start writing unit tests before you write any code.

Usually, you create a separate unit test project for each project.

I follow the Given/When/Then method of writing unit tests where:

  • Given is the name of the folder
  • When is the name of .cs file
  • and Then is the name of each method name explaining the behavior of the unit test.

You'll see some examples below when we get to each MVC term.

How Do You Unit Test HttpContext and the Database?

Ahh the age old question about HttpContext...How can you mock HttpContext?

Mocking an HttpContext can be difficult to mock because you are expecting a certain result from server variables or referring urls. How do you test against an environment-based object?

Luckily, I already mentioned this in a previous post called How to Successfully Mock HttpContext that describes all of the ways to return relevant HTTP data back in your unit tests.

As for the database, I was able to come up with a way to mock a database relatively quick.

Finally, The ASP.NET MVC Unit Tests

Since there's a lot of technology "hooks" in ASP.NET MVC and I want to go over all of them with their corresponding unit tests.

Now let's get into some MVC unit testing.

Controllers

Controllers are the workhorses of MVC. The behavior we want to test with controllers are the action results or view model returned from them.

GivenAnAboutController\WhenRequestingTheAboutPage.cs

[TestClass]
public class WhenRequestingTheAboutPage
{
    [TestMethod]
    public void ThenReturnTheAboutViewModel()
    {
        // Arrange
        var controller = new AboutController();
        // Act 
        // No database calls in the Index() method.
        // Just setting the title.
        var result = controller.Index() as ViewResult;
        var model = result.Model as AboutViewModel;
        // Assert
        Assert.IsNotNull(result);
        Assert.AreEqual("About DanylkoWeb", model.Title);
    }
}

This is the simplest you can get from a controller. You pass a model into the View and it replaces what it needs to on the View.

But we all know that most controllers aren't this simple. We are accessing a database or something inside the controller, right?

Something like this:

public class FaqController : Controller
{
    private readonly IFaqRepository _repository;
    public FaqController(): this(new FaqRepository()) { }
    public FaqController(IFaqRepository repository)
    {
        _repository = repository;
    }
    public ActionResult Index()
    {
        var records = _repository.GetAll();
        var model = new FaqViewModel
        {
            FAQs = records
        };
        return View(model);
    }
}

We need to mock up a FaqRepository. So how do we do that? Using a mock framework. I used to use RhinoMocks, but switched over to Moq.

Here is how we would mock up a FaqRepository.

[TestMethod]
public void ThenReturnTheFaqViewModel()
{
    // Arrange
    var faqs = new List<Faq>
    {
        new Faq {Id = "1", Answer = "Home", Question = "Where Do you Live?"},
        new Faq {Id = "2", Answer = "Since I was 11", Question = "When did you start programming?"},
        new Faq {Id = "3", Answer = "In Pennsylvania", Question = "Where were you born?"}
    };
    var faqRepository = new Mock<IFaqRepository>();
    faqRepository.Setup(e => e.GetAll()).Returns(faqs.AsQueryable());
    var controller = new FaqController(faqRepository.Object);
    // Act 
    var result = controller.Index() as ViewResult;
    var model = result.Model as FaqViewModel;
    // Assert
    Assert.IsNotNull(result);
    Assert.AreEqual(3, model.FAQs.Count());
}

Whatever is passed into your controller or new'ed up in your controller that accesses data, make sure you can mock it up (not MUCK it up) and pass it in.

Action Filters

ActionFilters are attributes on each action method in a controller. This is where we experience HttpContext occurrences so we need a mocked up HttpContext, which can be found here.

Let's look at the SearchBotFilter we wrote a while back as an example.

public class SearchBotFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Request.Browser.Crawler)
        {
            filterContext.Result = new ViewResult() { ViewName = "NotFound" };
        }
    }
}

This isn't that intimidating. We just need to confirm that the value returned is the right ViewName.

[TestMethod]
public void ThenConfirmAuthorizationActionFilterWorkedAsExpected()
{
    // Arrange
    var moqContext = new Mock<HttpContextBase>();
    var moqRequest = new Mock<HttpRequestBase>();
    moqContext.Setup(x => x.Request).Returns(moqRequest.Object);
    moqContext.Setup(x => x.Request.Browser.Crawler).Returns(true);
    var raa = new SearchBotFilter();
    var filterContextMock = new Mock<ActionExecutedContext>();
    var viewResultMock = new Mock<ViewResultBase>();
    filterContextMock.Object.Result = viewResultMock.Object;
    filterContextMock.Setup(e => e.HttpContext).Returns(moqContext.Object);
    // Act
    raa.OnActionExecuting(filterContextMock.Object);
    var viewResult = filterContextMock.Object.Result as ViewResult;
    // Assert
    Assert.IsTrue(viewResult.ViewName == "NotFound");
 
}

Action Results

Action Results are used to return the proper data to a View. The expected behavior for ActionResults is to determine the proper model sent to the View.

[TestMethod]
public void AboutIndexTest()
{
    // Arrange
    var controller = new AboutController();
    // Act
    var result = controller.Index();
    // Assert
    Assert.IsInstanceOfType(result, typeof(ViewResult));
    var viewResult = result as ViewResult;
    Assert.AreEqual(typeof(AboutViewModel), viewResult); }

You can optionally check that the proper model is returned as well.

Model Binders

If you are posting data back to an action method in a controller and the data in the form requires a specific model (i.e. Uploading a file), you can easily use ModelBinders to package everything into a nice model to pass it into a controller. The behavior expected is a model passed into the controller.

I posted about unit testing ASP.NET MVC model binders in a past post so I defer you to that post. :-)

UrlHelpers

UrlHelpers are great for cataloging your web application with Urls. We expect a string returned based on parameters passed into the UrlHelper.

There is one problem with UrlHelpers...they take a lot of setup to retrieve the Url to properly test.

Let me show you what I mean.

[TestMethod]
public void ThenVerifyTheRightLoginUrlIsReturned()
{
    // Arrange
    var routes = new RouteCollection();
    RouteTable.Routes.Clear();
    var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
    request.SetupGet(x => x.ApplicationPath).Returns("/");
    request.SetupGet(x => x.Url).Returns(new Uri("http://localhost/login"UriKind.Absolute));
    request.SetupGet(x => x.ServerVariables).Returns(new NameValueCollection());
    var response = new Mock<HttpResponseBase>(MockBehavior.Strict);
    response.Setup(x => x.ApplyAppPathModifier("/login")).Returns("http://localhost/login");
    var context = new Mock<HttpContextBase>(MockBehavior.Strict);
    context.SetupGet(x => x.Request).Returns(request.Object);
    context.SetupGet(x => x.Response).Returns(response.Object);
    var controller = new LoginController();
    controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
    controller.Url = new UrlHelper(new RequestContext(context.Object, new RouteData()), routes);
    // Act
    var login = controller.Url.LoginUrl();
    // Assert
    Assert.AreEqual("/Login", login);
}

See what I mean? It would be easier to create an TestInitialize at the top containing all of this plumbing and just issue it once.

HtmlHelpers

HtmlHelpers are used for returning properly-formatted HTML snippets of code. The behavior we are expecting back is properly-formatted HTML based on criteria passed into the helper.

HtmlHelpers are no better. They also take a lot of set up to unit test properly. I would also like to thank Thomas Ardel for his HtmlHelper routine. Awesome work, sir! :-)

[TestMethod]
public void ThenVerifyTheRightLoginUrlIsReturned()
{
    // Arrange
    var faq = new Faq
    {
        Id = "2",
        Answer = "Home",
        Question = "Where do you live?"
    };
    HtmlHelper helper = CreateHtmlHelper<Faq>(
        new ViewDataDictionary(faq));
    
    // Act
    var result = helper.EditFAQItem(faq);
    // Assert
    Assert.AreEqual("<a href=\"#2\">Faq Item #2</a>", result);
}
public HtmlHelper<T> CreateHtmlHelper<T>(ViewDataDictionary viewData) {     var cc = new Mock<ControllerContext>(         new Mock<HttpContextBase>().Object,         new RouteData(),         new Mock<ControllerBase>().Object);     var mockViewContext = new Mock<ViewContext>(         cc.Object,         new Mock<IView>().Object,         viewData,         new TempDataDictionary(),         TextWriter.Null);     var mockViewDataContainer = new Mock<IViewDataContainer>();     mockViewDataContainer.Setup(v => v.ViewData).Returns(viewData);     return new HtmlHelper<T>(         mockViewContext.Object, mockViewDataContainer.Object); }

A lot of work is required, but it can be tested!

Routes

Routing tells MVC which controller to use when a Url is entered. The expected behavior of a route is to know the controller, action, and optional id for when a Url comes in.

I discussed the routing unit testing in the post called How to Successfully Mock HttpContext (Yes, the same one from above).

The routing is pretty simple. 

Repository and Unit of Work (with Entity Framework)

The Repository and Unit of Work design patterns are great for accessing your databases. So how do you test the database without connecting to it?

The first thing you need is to create each of your mocking components from the lowest to the highest.

Our first thing we need is the "database." When I say database, I mean an in-memory table that provides the functionality we need for our unit test to pass. Of course, there are quicker ways to mock up a database.

For now, we'll use a simple in-Memory database using a mocked-up DbContext.

You may be wondering why we created a "fake" DbContext. The whole idea is to create a fake DbContext so we can test the Repository pattern and Unit of Work. That's the goal for mocking up a DbContext.

[TestMethod]
public void ThenReturnAllMenuItemsTest()
{
    // Arrange
    
    // __context is a Mocked DbContext created with Moq
    // Create our repository by passing in our DbContext.
    var mockMenuRepository = new Mock<MenuRepository>(_context.Object);
    // Mock unit of work has a lot of repositories in it.
    //  We pass the context into the Unit of Work.
    var mockUnitOfWork = new Mock<BaseUnitOfWork>(_context.Object);
    
    // If we call our MenuRepository in our unit of work, we return a mocked up MenuRepository.
    mockUnitOfWork.Setup(e => e.MenuRepository).Returns(mockMenuRepository.Object);
    // Act
    var menuItems = mockUnitOfWork.Object.MenuRepository.GetAll();
    // Assert
    Assert.AreEqual(5, menuItems.Count());
}

By setting up the three items to make a complete database call (DbContext, Repository, and Unit of Work), you can create an in-memory database of records to test any scenario that arises in a production environment.

Conclusion

After everything we covered in this post, there are always more ways to add unit tests for additional code coverage.

The great thing about ASP.NET MVC is that everything in the framework is unit testable. Of course, you can paint yourself into a corner if your design isn't completely flushed out and well understood.

If I missed any ASP.NET MVC technology that wasn't covered here, post a comment so we can get it into this Ultimate Guide to Unit Testing in ASP.NET MVC.

Post your comments below and Share this if you like it!

ASP.NET 8 Best Practices on Amazon

ASP.NET 8 Best Practices by Jonathan Danylko


Reviewed as a "comprehensive guide" and a "roadmap to excellence" with over 120 Best Practices for ASP.NET Core 8, Jonathan's first book by Packt Publishing explores proven techniques for every phase of the SDLC.

Learn industry-standard concepts to improve your coding, debugging, and deployment of ASP.NET Core websites.

Order now on Amazon.com button

Picture of Jonathan "JD" Danylko

Jonathan "JD" Danylko is an author, web architect, and entrepreneur who's been programming for over 30 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus