I'm in the process of upgrading our ASP.NET MVC Preview 5 app to Beta. Been quite painless so far, but hit a snag with an unmentioned change to the signature of the ViewContext class.
We've followed many by monkeypatching the HtmlHelper class further, extending it to a variety of uses. Obviously we need to test these extensions, so need a reference to an HtmlHelper instance. We used to have the following helper method to get an HtmlHelper object:
public HtmlHelper CreateHtmlHelper(ViewDataDictionary viewData)
{
var sw = new StringWriter();
var rd = new RouteData();
var tc = new TestController();
var td = new TempDataDictionary();
var tv = new TestView();
var req = new HttpRequest("", "http://localhost/", "");
var res = new HttpResponse(sw);
var hc = new HttpContext(req, res);
var hcw = new HttpContextWrapper(hc);
var rc = new RequestContext(hcw, rd);
var cc = new ControllerContext(rc, tc);
var vc = new ViewContext(cc, "View", viewData, td);
return new HtmlHelper(vc, tv);
}
I'd been aware of this method (had stumbled across it when certain tests seemed to be taking longer than they should), thought it looked pretty dodgy, ignored it, and added it to the growing list of technical debt. "Well it isn't broken..." I've joked with my teammate responsible about adding it the daily wtf, but we've both agreed we've both seen worse.
The beta of ASP.NET MVC has changed the ViewContext constructor to now require an IView and not a view name string, which fortunately broke the above, allowing me to reclaim some debt. We're using Moq, which allowed the following, much simpler, method.
public static HtmlHelper CreateHtmlHelper(ViewDataDictionary viewData)
{
var mockViewContext = new Mock<ViewContext>(new Mock<HttpContextBase>().Object,
new RouteData(),
new Mock<ControllerBase>().Object,
new Mock<IView>().Object,
viewData,
new TempDataDictionary());
var mockViewDataContainer = new Mock<IViewDataContainer>();
mockViewDataContainer.Expect(v => v.ViewData).Returns(viewData);
return new HtmlHelper(mockViewContext.Object, mockViewDataContainer.Object);
}
The HtmlHelper requires a ViewContext, and an IViewDataContainer. Mocking the ViewContext is clearly the most work, but not that dificult. In certain circumstances the HtmlHelper needs to get the ViewDataDictionary off the IViewDataContainer, so the Mock of the container above returns the one we fill with test data, which is passed into the method. This might not be necessary, depending on your situation.
If your helper extensions need more from the HttpContext (such as the Request), obviously you'll need to set those expectations too, ours currently don't. Ben Hall has an similar implementation using RhinoMocks (which would need to be changed to cater for the IView requirement), which sets expectations allowing the Resolve method to be used.
Update: I've since realised that one doesn't really 'Mock' an extension method (or any method for that matter), so don't call me out on the title!
Technorati Tags:
ASP.NET MVC,
TDD,
Moq