Unit Testing .NET MVC Controllers

I've written web apps in a number of languages over the years - PHP, Java, Ruby, and .NET. I haven't always (read never) been good about unit testing, though. I often set out with the intent of testing, but then get frustrated with the tests for one reason or another and quit.

Recently, on a .NET MVC project, I've been forcing myself to get into the "test first" habit - writing my unit tests then writing my code, but I found myself getting frustrated again. This time it was because unit testing my controllers required so much mocking and scaffolding to make their methods testable. Particularly methods that use Request.Form or TryUpdateModel. To solve this problem (and to keep from repeating myself every time I test a new controller), I ended up writing an abstract base class to take care of the controller setup. Here's the code...

AbstractControllerTest.vb

Imports MoqImports System.WebImports System.Web.MvcImports System.Web.RoutingPublic MustInherit Class AbstractControllerTest    Protected _httpContextMock As Mock(Of HttpContextBase)    Protected _httpRequestMock As Mock(Of HttpRequestBase)    Protected _httpSessionMock As Mock(Of HttpSessionStateBase)    Protected Sub InitController(ByVal ctlr As Controller, ByVal formValues As Dictionary(Of String, String))        Dim formData As New Collections.Specialized.NameValueCollection()        For Each kv As KeyValuePair(Of String, String) In formValues            formData.Set(kv.Key, kv.Value)        Next        _httpContextMock = New Mock(Of HttpContextBase)        _httpRequestMock = New Mock(Of HttpRequestBase)        _httpSessionMock = New Mock(Of HttpSessionStateBase)        _httpRequestMock.Setup(Function(r) r.Form).Returns(formData)        _httpContextMock.Setup(Function(c) c.Request).Returns(_httpRequestMock.Object)        _httpContextMock.Setup(Function(c) c.Session).Returns(_httpSessionMock.Object)        ctlr.ControllerContext = New ControllerContext(_httpContextMock.Object, New RouteData(), ctlr)        ctlr.ValueProvider = New FormValueProvider(ctlr.ControllerContext)    End Sub    Protected Sub InitController(ByVal ctlr As Controller)        InitController(ctlr, New Dictionary(Of String, String))    End Sub    Protected Sub CleanupController()        _httpContextMock = Nothing        _httpRequestMock = Nothing        _httpSessionMock = Nothing    End SubEnd Class

The class is pretty simple. It gives classes that inherit from it two methods: an overloaded InitController method, which initializes an MVC controller class with various HTTP-related mocks, and properties and a CleanupController method to perform cleanup after each test is run. (I'm using the excellent Moq library for mocking - http://code.google.com/p/moq/)

Here's a sample unit test that shows how to use it...

Imports System.TextImports System.WebImports System.Web.MvcPublic Class SampleTest    Inherits AbstractControllerTest    ...Code omitted for brevity...        Public Sub MyTestCleanup()        CleanupController()    End Sub        Public Sub Test_DoSomething()        'Form data that you would expect from an HTTP form post        Dim formData As New Dictionary(Of String, String) From {            {"FirstName", "Jason"},            {"LastName", "Polete"}        }        'Create your controller        Dim ctlr As New SampleController()        'Method inherited from AbstractControllerTest        'Initializes your controller with mocks of        'HttpContextBase, HttpRequestBase, and HttpSessionStateBase        InitController(ctlr, formData)        'Call method        Dim view As ViewResult = ctlr.DoSomething()        'Assert        Assert.AreEqual("Jason", view.ViewData("FirstName"))    End SubEnd Class

What's going on...

  1. Line 6: We inherit from AbstractControllerTest
  2. Line 11: We call the inherited method CleanupController to reset the context after each test
  3. Lines 15 - 19: We set up some form data. In this hypothetical sample the DoSomething method expects two values posted from an HTML form: FirstName and LastName
  4. Line 27: We call InitController. This sets up the various mocks and properties for the controller such as HttpContextBase, HttpRequestBase, and HttpSessionStateBase.

So by inheriting from this base class, all of the tedious setup you would normally have to do by hand is done for you. Also, because the mocks in the abstract class have protected access, you can access them to perform any additional setup that you may need to do. For example, if the method you are testing expects the session to contain a value under the key "SomeSessionKey", you can setup the mock as follows.

_httpSessionMock.Setup(Function(s) s.Item("SomeSessionKey")).Returns("MySessionData")

This class is pretty basic, but it has made testing controller much less frustrating for me. Hopefully others will find it helpful too. If you have any questions, corrections, additions or comments, feel free.

 

āœ
comments powered by Disqus