Wednesday, May 13, 2015

Human Readable Code - Fluently Mocking the ASP.NET MVC ControllerContext

I was recently writing some unit tests against an MVC controller action which required a user to be authenticated via Windows Authentication. The test was initially failing as it was not able to access the controller action. This was happen because the context in which the test was being run in did not have an authenticated user.

I resolved this issue by mocking the ControllerContext so the controller "believed" it was being accessed by an authenticated user. Using the Moq mocking framework, this can be accomplished using the following mocking code:

var mockControllerContext = new Mock<ControllerContext>();
     .SetupGet(c => c.HttpContext.User.Identity.Name)
     .SetupGet(c => c.HttpContext.User.Identity.IsAuthenticated)

var homeController = new HomeController();
homeController.ControllerContext = mockControllerContext.Object;

This implementation does the job but it is not particularly readable at a glance. You have to read through each statement to understand which property the mock is setting up a return value for. Unit tests are a great way to understand the workings of a code base as they allow you to see at a high level how the code base is supposed to behave under certain circumstances. However, this benefit is dramatically reduced if the unit tests themselves are long winded and not easy to understand. 

After watching Cory House’s Pluralsight course on Clean Code: Writing Code for Humans I have become acutely aware of the benefits of making your code more human readable. Extension methods are a good way to achieve a more readable syntax and led me to refactoring the above mocking code to the following:

var mockControllerContext = new Mock<ControllerContext>()

I was able to create this fluent syntax by creating a couple of extension methods that could be chained together to update the mocked ControllerContext object as required.

public static Mock<ControllerContext> WithIdentityNameAs(
     this Mock<ControllerContext> mockControllerContext, string username)
          .SetupGet(p => p.HttpContext.User.Identity.Name)

 return mockControllerContext;

public static Mock<ControllerContext> IsAuthenticated(
     this Mock<ControllerContext> mockControllerContext)
          .SetupGet(p => p.HttpContext.User.Identity.IsAuthenticated)

 return mockControllerContext;

The key to writing extension methods that allow method chaining is to return the type which is passed in that the extension method extends. In the case of my extension methods, I had to return the mockControllerContext object or an instance of a Mock<ControllerContext> type as this is what was being extended. As you are returning the same type which your extension method extends, it allows you to call another extension method on that returned object and chain your extension methods together. This chaining makes nice, succinct, readable expressions.

The main draw back with this approach is that you have now moved test setup code away from the unit test itself into a separate extension method helper class. This is not necessarily a bad thing. It helps promote code reuse as you can use these extension methods in many other unit tests without having to duplicate the mock setup code. 

On a separate note, the refactored implementation gives the reader the option of deciding what level of abstraction they want to read the code at. If they only want to understand what is taking place in the test setup, reading the extension method names will be sufficient. However, if they are interested in how these values are actually setup in the mocks, they have the option of diving deeper into the extension methods themselves to view the implementation details. The key point here is that the reader has this option. My first implementation forced the reader to read the details to understand what test setup was taking place, where as my second implementation abstracted these details away. Providing these levels of abstraction to the reader is another technique which can help make your code more readable.

No comments:

Post a Comment