Html helpers for ASP.NET MVC are static extension methods, which frequently reference the ViewContext and HttpContext. Combined, this can make unit testing a bit tricky. Let’s write a new Html helper using a test-first methodology. Let’s start with a prototype function:
public static MvcHtmlString MyTable(this HtmlHelper helper, MyModel model, IDictionary<string, object> htmlAttributes) { return MvcHtmlString.Empty; }
I’ve added just enough code here to get the prototype to compile. Now let’s write some unit tests. First we need to validate the arguments. I’m using MSTest here:
[TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void MyTable_should_throw_when_helper_argument_null() { var model = new MyModel(); MyHelpers.MyTable(null, model, null); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void MyTable_should_throw_when_model_argument_null() { var hh = new HtmlHelper(null, null); MyHelpers.MyTable(hh, null, null); }
Note that I have elected to call this method as a regular static method rather than as an extension method. Either one is valid, but since I am unit testing the case where the HtmlHelper argument is null, it makes more sense to call the method under test as a regular static method.
Those tests will fail when run, so I’ll write some code to fix that:
public static MvcHtmlString MyTable(this HtmlHelper helper, MyModel model, IDictionary<string, object> htmlAttributes) { if (helper == null) { throw new ArgumentNullException("helper"); } if (model == null) { throw new ArgumentNullException("model"); } return MvcHtmlString.Empty; }
Now let’s presume that the requirements of the new method specify that the table should get its HTML id from the HttpContext.Items. This is of course a very stupid place to store an HTML id, but I want to demonstrate how to fake the HttpContext, since that is something that people often find difficult. So let’s make a fake HttpContext. I’ll need a lightweight implementation of IViewDataContainer, as well, for reasons which will be clear later on:
private class FakeHttpContext : HttpContextBase { private Dictionary<object, object> _items = new Dictionary<object, object>(); public override IDictionary Items { get { return _items; } } } private class FakeViewDataContainer : IViewDataContainer { private ViewDataDictionary _viewData = new ViewDataDictionary(); public ViewDataDictionary ViewData { get { return _viewData; } set { _viewData = value; } } }
I elected to only implement the Items property of HttpContextBase, since that is the only one I’ll need to reference in the implementation of my helper. Were I to reference other HttpContextBase properties which I did not override, I would expect to see a NotImplementedException at runtime.
With these two, small classes, I can write a test for the helper which will execute without throwing any exceptions. Well, except for the fact that the assertion fails:
[TestMethod] public void MyTable_should_render_HTML_table_with_id_from_http_context() { var vc = new ViewContext(); vc.HttpContext = new FakeHttpContext(); vc.HttpContext.Items.Add(MyHelpers.IdKey, "foo"); var hh = new HtmlHelper(vc, new FakeViewDataContainer()); var model = new MyModel(); var result = MyHelpers.MyTable(hh, model, null).ToString(); Assert.AreEqual("<table id = \"foo\"></table>", result); }
I needed the IViewDataContainer because the two argument constructor for HtmlHelper will throw ArgumentNullException if you pass a null for the second argument.
Finally, I can implement the helper to make this unit test pass:
public static MvcHtmlString MyTable(this HtmlHelper helper, MyModel model, IDictionary<string, object> htmlAttributes) { if (helper == null) { throw new ArgumentNullException("helper"); } if (model == null) { throw new ArgumentNullException("model"); } TagBuilder tagBuilder = new TagBuilder("table"); tagBuilder.MergeAttributes(htmlAttributes); tagBuilder.GenerateId(helper.ViewContext.HttpContext.Items[MyHelpers.IdKey]);return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal)); }
{ 2 } Comments
Can you program a puzzle like the one at acrostics.org ? I need such a program and am seeking a programmer who will work with/for me.
Thanks! This is exactly what I needed, it was not really clear to me with unit testing of extension.. namely faking IViewDataContainer.
{ 1 } Trackback
[...] http://blogs.teamb.com/craigstuntz/2010/09/10/38638 [...]
Post a Comment