Click here to Skip to main content
15,868,016 members
Articles / DevOps / Testing

Unit testing Controllers using NUnit and NSubstitute

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
5 Jan 2016CPOL3 min read 17.6K   9  
Article discussing how to unit test ASP.NET MVC controllers using NUnit and NSubstitute

In my previous article I outlined a way to unit test HttpContext.Current. You can read about that here:

Unit testing HttpContext.Current outside of a Controller

This was in code outside of a Controller. But what happens if we’re within a Controller? Inside a Controller HttpContext is abstract because it is of type HttpContextBase. This means we can mock it. But how do we achieve that? How do we hook into those internal Controller properties? We need to inject a mocked HttpContextBase into our Controller somehow. We can do this by overriding the ControllerContext. In this post I’ll share the NUnit template I use for testing Controllers. I'll show you how to create a fake HttpContext. I'll also detail how to mock out the dependencies using NSubstitute.

The first thing we need is our testing frameworks, which we’ll install via Nuget. Open the Nuget Package Manager Console. It lives here: Tools > Nuget Package Manager > Package Manager Console. We need to install NUnit and NSubstitute.

Install-Package NUnit
Install-Package NSubstitute

Now they’re installed, let’s look at a generic test template first. I use this for every test that isn't a Controller test. The Controller test template builds on this one so we need a quick look at this.

using NUnit.Framework;

[TestFixture]
public abstract class TestTemplate<TContext>
{
    protected virtual TContext Sut { get; private set; }

    [SetUp]
    public virtual void MainSetup()
    {
        Sut = EstablishContext();
    }

    [TearDown]
    public void Teardown()
    {
        TestCleanup();
    }

    protected abstract TContext EstablishContext();

    protected abstract void TestCleanup();
}

There's a couple of interesting things going on here. We've got a property called Sut. That just means Subject Under Test. It's a quick way of giving us access to the object that we're testing. When we create a test we have to override EstablishContext. This returns an instance of the type we want to test and assigns it to Sut. We've got a TestCleanup method where we can perform any test clean-up we need. Notice also that our MainSetup method is virtual. This allows us to override it in our ControllerTestTemplate. Let's look at that right now.

using System;
using System.Collections;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using NSubstitute;

public abstract class ControllerTestTemplate<tcontext> : TestTemplate<tcontext> where TContext : Controller
{
    private const string RootPath = "/basepath";

    protected static Uri BaseUri
    {
        get { return new Uri("http://localhost:4000"); }
    }

    protected HttpContextBase HttpContextSub { get; private set; }
    protected HttpRequestBase RequestSub { get; private set; }
    protected HttpResponseBase ResponseSub { get; private set; }

    public override void MainSetup()
    {
        base.MainSetup();
        Sut.ControllerContext = FakeControllerContext(Sut);
        Sut.Url = new UrlHelper(Sut.ControllerContext.RequestContext);
    }

    private ControllerContext FakeControllerContext(ControllerBase controller)
    {
        InitialiseFakeHttpContext();
        var routeData = new RouteData();
        routeData.Values.Add("key1", "value1");
        return new ControllerContext(HttpContextSub, routeData, controller);
    }

    private void InitialiseFakeHttpContext(string url = "")
    {
        HttpContextSub = Substitute.For<HttpContextBase>();
        RequestSub = Substitute.For<HttpRequestBase>();
        ResponseSub = Substitute.For<HttpResponseBase>();
        var serverUtilitySub = Substitute.For<HttpServerUtilityBase>();
        var itemsSub = Substitute.For<IDictionary>();
        HttpContextSub.Request.Returns(RequestSub);
        HttpContextSub.Response.Returns(ResponseSub);
        HttpContextSub.Server.Returns(serverUtilitySub);
        HttpContextSub.Items.Returns(itemsSub);
        serverUtilitySub.MapPath("/virtual").Returns("c:/absolute");
        RequestSub.ApplicationPath.Returns(RootPath);
        RequestSub.Url.Returns(BaseUri);
        if (!string.IsNullOrEmpty(url))
        {
            RequestSub.AppRelativeCurrentExecutionFilePath.Returns(url);
        }
    }
}

A couple of things to note here. First up, I'm mocking the Items property. This is because the AccountController uses it within the GetOwinContext() extension method. Also notice that I'm exposing properties like Request, Response and HttpContext in the template. This allows us to use them in our tests if required, as we'll see in a moment. We can expose as many of the properties as we need for our tests. Making calls to Url.Action that you need to test? Add a UrlHelper property and initialise it in MainSetup.

That's the templates covered. Now how do I use them?

Now that we have our template, let's write a couple of tests that uses it.

We'll write a simple test for now that illustrates the template in action. Let's say we have a controller action in our HomeController for displaying custom 404 errors. Here's what it might look like:

public ActionResult Error404()
{
    Response.StatusCode = (int)HttpStatusCode.NotFound;
    Response.TrySkipIisCustomErrors = true;

    if (Request.IsAjaxRequest())
    {
        return Json(new { message = "Not found." }, JsonRequestBehavior.AllowGet);
    }

    return View();
}

Without our template we get a NullReferenceException on the first line. If we use our template, the error goes away. Not only that, but we can assert that the properties are correct on the Response. Here's a couple of tests that we might write to verify that our Response is correct.

using System.Net;
using NUnit.Framework;
using Painstorming.Web.Controllers;

[TestFixture]
public class HomeControllerTests : ControllerTestTemplate<homecontroller>
{
    protected override HomeController EstablishContext()
    {
        // if our Controller has any dependencies, we mock those here and pass them in
        return new HomeController();
    }

    protected override void TestCleanup()
    {
    }

    [Test]
    public void GivenAHomeController_WhenGettingFromTheError404Action_ThenTheResponseStatusCodeShouldBeNotFound()
    {
        Sut.Error404();

        Assert.That(ResponseSub.StatusCode, Is.EqualTo((int) HttpStatusCode.NotFound));
    }

    [Test]
    public void GivenAHomeController_WhenGettingFromTheError404Action_ThenTheResponseTrySkipIisCustomErrorsShouldBeTrue()
    {
        Sut.Error404();

        Assert.That(ResponseSub.TrySkipIisCustomErrors, Is.True);
    }
}

What did we just do?

There was quite a lot going on here. Let's recap:

  • We installed a couple of Nuget packages to help with our testing
  • We created a base TestTemplate for all of our unit tests
  • We created a ControllerTestTemplate for our Controller tests
  • We wrote a couple of tests for a Controller action that handles displaying a 404 error page

View original article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead Levelnis Ltd
United Kingdom United Kingdom
Follow along my journey as I create a newsletter about launching websites. Just message me with "I'm in" and I'll add you

Comments and Discussions

 
-- There are no messages in this forum --