23

Is there any way to (unit) test my own HtmlHelpers? In case when I'd like to have custom control (rendered by HtmlHelper) and I know requierements for that control how could I write tests first - and then write code? Is there a specific (nice) way to do that?

Is it worth?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
rafek
  • 5,464
  • 13
  • 58
  • 72

3 Answers3

30

The main problem is that you have to mock the HtmlHelper because you may be using methods of the helper to get routes or values or returning the result of another extension method. The HtmlHelper class has quite a lot of properties and some of them quite complex like the ViewContext or the current Controller.

This post from Ben Hart that explains how to create such a mock with Moq. Can be easily translated to another mock framework.

This is my Rhino Mocks version adapted to the changes in the MVC Framework. It's not fully tested but it's working for me but don't expect perfect results:

    public static HtmlHelper CreateHtmlHelper(ViewDataDictionary viewData)
    {
        var mocks = new MockRepository();

        var cc = mocks.DynamicMock<ControllerContext>(
            mocks.DynamicMock<HttpContextBase>(),
            new RouteData(),
            mocks.DynamicMock<ControllerBase>());

        var mockViewContext = mocks.DynamicMock<ViewContext>(
            cc,
            mocks.DynamicMock<IView>(),
            viewData,
            new TempDataDictionary());

        var mockViewDataContainer = mocks.DynamicMock<IViewDataContainer>();

        mockViewDataContainer.Expect(v => v.ViewData).Return(viewData);

        return new HtmlHelper(mockViewContext, mockViewDataContainer);
    }
brianpeiris
  • 10,735
  • 1
  • 31
  • 44
Marc Climent
  • 9,434
  • 2
  • 50
  • 55
  • 2
    sadly the blog post errors but the above explains quite well what you need to do – MJJames Aug 07 '10 at 18:43
  • 1
    Looks like they have changed the MVC framework slightly since the above as ViewContext now takes a TextWriter as well – Rob West Dec 22 '10 at 14:12
  • 4
    This is super helpful, but testing against MVC3 I had to tweak this by adding a new parameter to the mockViewContextConstructor (I used `TextWriter.Null`) and instead of the mockViewDataContainer, I had to set ViewData explicitly (e.g., `mockViewDataContainer.ViewData = viewData;`) or Rhino Mocks throws an exception. – Brandon Linton Aug 24 '11 at 21:10
  • Has anyone tested a helper method that called htmlhelper.partial? Everything works until it calls that, then throws some error, something to do with needing to return something back with tempdata. Why did they make this stuff so hard to test? – nportelli Sep 28 '11 at 20:17
9

If anyone is looking for how to create HtmlHelper<T> (that's what I was after), here is an implementation that might help - my type is a class named Model:

public static HtmlHelper<Model> CreateHtmlHelper()
{
    ViewDataDictionary vd = new ViewDataDictionary(new Model());

    var controllerContext = new ControllerContext(new Mock<HttpContextBase>().Object,
                                                  new RouteData(),
                                                  new Mock<ControllerBase>().Object);

    var viewContext = new ViewContext(controllerContext, new Mock<IView>().Object, vd, new TempDataDictionary(), new Mock<TextWriter>().Object);

    var mockViewDataContainer = new Mock<IViewDataContainer>();
    mockViewDataContainer.Setup(v => v.ViewData).Returns(vd);

    return new HtmlHelper<Model>(viewContext, mockViewDataContainer.Object);
}

Or a more generic implementation:

    public HtmlHelper<T> CreateHtmlHelper<T>() where T : new()
    {
        var vd = new ViewDataDictionary(new T());

        var controllerContext = new ControllerContext(new Mock<HttpContextBase>().Object,
                                                      new RouteData(),
                                                      new Mock<ControllerBase>().Object);

        var viewContext = new ViewContext(controllerContext, new Mock<IView>().Object, vd, new TempDataDictionary(), new Mock<TextWriter>().Object);

        var mockViewDataContainer = new Mock<IViewDataContainer>();
        mockViewDataContainer.Setup(v => v.ViewData).Returns(vd);

        return new HtmlHelper<T>(viewContext, mockViewDataContainer.Object);
    }
CRice
  • 12,279
  • 7
  • 57
  • 84
0

I'm creating a custom helper, and this is the code i'm using to mock the httphelper with Moq and ASP MVC 2. I'm also passing as a parameter a mock of the HttpRequestBase. You can remove that if you don't need it

public static HtmlHelper CreateHtmlHelper(ViewDataDictionary viewData, Mock requestMock)
        {
            var contextBaseMock = new Mock();
            contextBaseMock.SetupGet(cb => cb.Request).Returns(requestMock.Object);

            var cc = new ControllerContext(contextBaseMock.Object,
                                            new RouteData(),
                                            new Mock().Object);
            var vctx = new ViewContext(
                cc,
                new Mock().Object,
                viewData,
                new TempDataDictionary(),
                new HtmlTextWriter(new StreamWriter(new MemoryStream())));

            var mockViewDataContainer = new Mock();

            mockViewDataContainer.SetupGet(v => v.ViewData).Returns(viewData);

            return new HtmlHelper(vctx, mockViewDataContainer.Object);
        }
Razvan Dimescu
  • 462
  • 4
  • 10