Monday, 18 February 2013

Unit testing MVC controllers / Mocking UrlHelper

I have put a name to my pain...

And it is unit testing ASP.Net MVC controllers.

Well perhaps that's unfair. I have no problem unit testing MVC controllers.... until it comes to making use of the "innards" of MVC. Let me be more specific. This week I had a controller action that I needed to test. It looked a little like this:

Looks fine right? It's an action that takes a simple object as an argument. That's ok. It returns a JsonResult. No worries. The JsonResult consists of an anonymous class. De nada. The anonymous class has one property that is driven by the controllers UrlHelper. Yeah that shouldn't be an issue... Hold your horses sunshine - you're going nowhere!

Getting disillusioned

Yup, the minute you start pumping in asserts around that UrlHelper driven property you're going to be mighty disappointed. What, you didn't expect the result to be null? Damn shame.

Despite articles on MSDN about how the intention is for MVC to be deliberately testable the sad fact of the matter is that there is a yawning hole around the testing support for controllers in ASP.Net MVC. Whenever you try to test something that makes use of controller "gubbins" you have serious problems. And unfortunately I didn't find anyone out there who could offer the whole solution.

After what I can best describe as a day of pain I found a way to scratch my particular itch. I found a way to write unit tests for controllers that made use of UrlHelper. As a bonus I managed to include the unit testing of Routes and Areas (well kind of) too.

MvcMockControllers updated

This solution is heavily based on the work of Scott Hanselman who wrote and blogged about MvcMockHelpers back in 2008. Essentially I've taken this and tweaked it so I could achieve my ends. My version of MvcMockHelpers looks a little like this:

What I want to test

I want to be able to unit test the controller Edit method I mentioned earlier. This method calls the Action method on the controllers Url member (which is, in turn, a UrlHelper) to generate a URL for passing pack to the client. The URL generated should fit with the routing mechanism I have set up. In this case the route we expect a URL for was mapped by the following area registration:

Enough of the waffle - show me a unit test

Now to the meat; here's a unit test which demonstrates how this is used:

Let's go through this unit test and breakdown what's happening:

  1. Arrange
    • We instantiate an int that represents part of the route data.
    • We clear out any registered routes from the RouteTable so we're starting with a clean slate.
    • We manually register our specific areas (only 1 in this example). This is done because AreaRegistration.RegisterAllAreas() simply doesn't work in the unit test environment. There may be a better solution, one that allows for dynamic area registration but I don't know what it is. This works.
    • We register our routes. (By the way, isn't it frustrating that RouteConfig.RegisterRoutes(RouteTable.Routes) works where AreaRegistration.RegisterAllAreas() doesn't?)
    • We create a MockHttpContext passing in the URL that would represent the "calling page"; that is to say the URL which would invoke the action we wish to test.
    • Using the MockHttpContext and the routes we set up we determine the route data.
    • We set the controllers context to the MockHttpContext and pass in the configured routes and the inferred route data.
  2. Act
    • We call the Edit method on the controller and pass in a newed-up AnObject object.
  3. Assert
    • Check that the correct area was registered.
    • Check that the output from the Edit method is as you would expect.

The most interesting thing you'll note is the controller's UrlHelper is now generating a URL as we might have hoped. The URL is generated making use of our routing, yay! Finally we're also managing to unit test a route registered by our area.

Wednesday, 13 February 2013

Using Expressions with Constructors

Every now and then you think "x should be easy" - and it isn't. I had one of those situations this morning. Something I thought would take 5 minutes had me still pondering 30 minutes later. I finally cracked it (with the help of a colleague - thanks Marc!) and I wanted to note down what I did since I'm sure to forget this.

So what's the problem?

In our project we had a very simple validation class. It looked a bit like this:

I wanted to take this class and extend it to have a constructor which allowed me to specify a Type and subsequently an Expression of that Type that allowed me to specify a property. 10 points if you read the last sentence and understood it without reading it a second time.

Code is a better illustration; take a look below. I wanted to go from #1 to #2:

"Why?" I hear you ask. Well we had a swathe of statements in the code which test each property for a problem and would create a FieldValidation with the very same property name if one was found. There's no real problem with that but I'm a man that likes to refactor. Property names change and I didn't want to have to remember to manually go through each FieldValidation keeping these in line. If I was using the actual property name to drive the creation of my FieldValidations then that problem disappears. And I like that.

So what's the solution?

Well it's this:

As you can see we have taken the original FieldValidation class and added in a generic constructor which instead of taking string fieldName as a first argument it takes Expression<Func<T, object>> expression. LINQ's Expression magic is used to determine the supplied property name which is smashing. If you were wondering, the first MemberExpression code is used for reference types. The UnaryExpression wrapping a MemberExpression code is used for value types. A good explanation of this can be found here.

My colleague directed me to this crucial StackOverflow answer which provided some much needed direction when I was thrashing. And that's it; we're done, home free.