Views are hard to test in MVC
I've been trying to write ASP.NET MVC code using Test Driven Development (TDD) principles. This has been going very well for the Models and Controllers of MVC, but I have been drawing a blank at the Views. It turns out that writing unit tests for views in ASP.NET MVC is hard. There simply are too many hidden dependencies on the ASP.NET environment built into the view engine to be able to build standalone tests around it.
A common response I have come across is that there shouldn't be any logic in the views, so there shouldn't be any need to write unit tests around them. This argument applies very well to views that are pure HTML which work from a strongly-typed view model, as in that case it should be sufficient to test that the appropriate view model has been created and populated with the correct values. However once you start introducing AJAX functionality into your application then the nice separation between view and controller logic starts to disappear.
- It starts to blur the line between test and production code, as the tests need to be run from within the same web project in order to have access to the script files used in the views;
- It becomes more inconvenient to run the tests: having to fire up a browser in order to run the automated tests for a part of the application creates an extra step and an extra psychological barrier to running the tests often. Personally, I'd like all my tests to be run from one place, ideally from within Visual Studio.
In addition, I want to be able to test the views on their own rather than having to run the whole system. Automated UI testing (using something like Selenium) is great for integration testing, but I want to be able to test the view logic in isolation from the rest of the application.
A possible solution
I think I have found a way get around this problem. Essentially what I am doing is dynamically generating the HTML of a view from the view path and view model. I can then use headless browser automation to execute the view using a virtual web browser, clicking on links and running scripts dynamically. This allows me to completely decouple my view logic from the controller logic, allowing me to set up views on demand without having to do things like access the database. I doubt I am the first to try something like this, but I haven't been able to find anything online to do view testing the way I would like, at least not in ASP.NET MVC. However, this could mean that I am going about this completely the wrong way, or that I'm just not very good at using Google!
Dynamically generating view HTML
After many hours of frustration, I have been forced to admit that any attempt to try to use ASP.NET MVC's inbuilt view engine from outside the ASP.NET environment (i.e. from within a test project) is doomed to failure. I tried several things, including attempting to mock the ControllerContext, and although I was able to get this working from within the MVC project, I was unable to get this approach to work from within a standalone project. There simply are too many hidden dependencies for this to work.
Razor Generator is available as a Visual Studio extension, but as I don't own a full copy of Visual Studio on my home machine, I needed to get this to work with Visual Web Developer. I was able to accomplish this by changing the extension of the .vsix file .zip, opening it in 7Zip, and editing the <SupportedProducts> section of the extension.vsixmanifest file to read:
<SupportedProducts> <VisualStudio Version="10.0"> <Edition>Ultimate</Edition> <Edition>Premium</Edition> <Edition>Pro</Edition> <Edition>Express</Edition> </VisualStudio> </SupportedProducts>I was then able to rename save it back as a .vsix file and open it in Visual Web Developer. This installed the extension without any problems as far as I can tell (Warning: do this at your own risk!)
- Razor Generator, as it stands, generates the HTML for each view, partial view and layout view separately, but I needed a way to render the whole page for a view, including the layout view and any partial views. To do this I had to download the source code and edit the PrecompiledMvcViews.Testing project. As it stands, it generates a placeholder value for any partial views referenced from the view page, so I modified this to include the generated HTML for each partial view instead. I also had to add some additional code to embed the view HTML within the generated HTML of its layout view.
Headless browser automation
Once I had the view HTML, I was able to use headless browser automation against it using the HTMLUnit library. This is a Java library, but as it the source is freely available I was able to compile it as a .NET dll using IKVM (using the approach described by Steven Sanderson), allowing me to use it in my test project. So if I have a view as follows:
Obviously it's still a work in progress: it doesn't handle routeData yet among other things, and some of the internals are a bit hacky at the moment, but if anyone's interested you can download the source from bitbucket and have a play; any feedback is appreciated, positive or negative.