34

Could somebody show me how you would go about creating a mock HTML Helper with Moq?

This article has a link to an article claiming to describe this, but following the link only returns an ASP.NET Runtime Error

[edit] I asked a more specific question related to the same subject here, but it hasn't gotten any responses. I figured it was too specific, so I thought I could get a more general answer to a more general question and modify it to meet my requirements.

Thanks

Community
  • 1
  • 1
DaveDev
  • 41,155
  • 72
  • 223
  • 385

4 Answers4

45

Here's another article that shows you how to achieve the same thing:

public static HtmlHelper CreateHtmlHelper(ViewDataDictionary vd)
{
  var mockViewContext = new Mock<ViewContext>(
    new ControllerContext(
      new Mock<HttpContextBase>().Object,
      new RouteData(),
      new Mock<ControllerBase>().Object),
    new Mock<IView>().Object,
    vd,
    new TempDataDictionary());

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

  return new HtmlHelper(mockViewContext.Object, mockViewDataContainer.Object);
}
Liam
  • 27,717
  • 28
  • 128
  • 190
Thomas
  • 7,933
  • 4
  • 37
  • 45
20

In MVC5, the ViewContext has an extra constructor parameter for a TextWriter, so Thomas' code no longer works. I added an in-memory TextWriter to get around this problem:

public static HtmlHelper CreateHtmlHelper(ViewDataDictionary vd)
{
    Mock<ViewContext> mockViewContext = new Mock<ViewContext>(
        new ControllerContext(
            new Mock<HttpContextBase>().Object,
            new RouteData(),
            new Mock<ControllerBase>().Object
        ),
        new Mock<IView>().Object,
        vd,
        new TempDataDictionary(),
        new StreamWriter(new MemoryStream())
    );

    Mock<IViewDataContainer> mockDataContainer = new Mock<IViewDataContainer>();
    mockDataContainer.Setup(c => c.ViewData).Returns(vd);

    return new HtmlHelper(mockViewContext.Object, mockDataContainer.Object);
}
CodingIntrigue
  • 75,930
  • 30
  • 170
  • 176
  • To use the ViewBag I changed the method signature to `public static HtmlHelper CreateHtmlHelper(ViewDataDictionary vd, object clientData)` and the return row to `var htmlHelper = new HtmlHelper(mockViewContext.Object, mockViewDataContainer.Object); htmlHelper.ViewBag.Client = clientData; return htmlHelper;` – LosManos Feb 26 '14 at 15:11
  • It is the best answer so far but it will not work if you have an html helper that use htmlHelper. For example, if you are using Html.action(...) this generate a null answer. – Patrick Desjardins Apr 19 '14 at 22:25
  • 2
    What is the use for `ViewContext` here. It appears that `vc` is never used. – Blaise May 14 '15 at 16:48
  • @Blaise You're right, it looks like it's not used. Thanks for the heads up – CodingIntrigue Jul 31 '15 at 08:10
  • 1
    You also need to set CallBase to true to make sure the ViewContext properties work correctly: `var mockViewContext = new Mock( new ControllerContext( new Mock().Object, new RouteData(), new Mock().Object), Mock.Of(), vd, new TempDataDictionary(), new StringWriter()) { CallBase = true };` – David Chandler Dec 21 '15 at 14:00
13

What you can do is this:

HtmlHelper helper = null;
helper.YourHelperMethod();

No need to mock anything. Works brilliant for me.

MrW
  • 1,210
  • 2
  • 16
  • 27
  • 33
    This only works if you've written a helper method that doesn't use the helper at all, obviously. If you try to get to the `ViewContext`, `RouteCollection`, or anything else this doesn't help you. – Matt Enright Aug 17 '12 at 13:33
  • 5
    This should not be the accepted answer. It only answer a single scenario and not the case that you have to use Html Helper. Like @MattEnright said, it will not help you if you need to use withing you test Html Helper to generate Action Link for example. – Patrick Desjardins Apr 19 '14 at 22:23
  • 1
    This is obviously a mocked answer -) – trailmax Nov 11 '14 at 11:37
  • 2
    This answer completely ignores the reality of trying to actually use the HtmlHelper in the method you are testing. Sounds like a good way to generate a NullReferenceException. – drz Jun 09 '16 at 20:22
1

To test disposable helper like BeginForm with access to ViewContext.Writer you can use this:

public static HtmlHelper CreateHtmlHelper(ViewDataDictionary vd, Stream stream = null)
{
    TextWriter textWriter = new StreamWriter(stream ?? new MemoryStream());
    Mock<ViewContext> mockViewContext = new Mock<ViewContext>(
        new ControllerContext(
            new Mock<HttpContextBase>().Object,
            new RouteData(),
            new Mock<ControllerBase>().Object
        ),
        new Mock<IView>().Object,
        vd,
        new TempDataDictionary(),
        textWriter
    );
    mockViewContext.Setup(vc => vc.Writer).Returns(textWriter);

    Mock<IViewDataContainer> mockDataContainer = new Mock<IViewDataContainer>();
    mockDataContainer.Setup(c => c.ViewData).Returns(vd);

    return new HtmlHelper(mockViewContext.Object, mockDataContainer.Object);
}
Lukáš Kmoch
  • 1,239
  • 11
  • 11
  • There must be something missing. I got a `System.NullReferenceException`. "Object reference not set to an instance of an object". Any idea? – Blaise May 14 '15 at 17:04
  • You probably need to make sure `CallBase` is set to true on the mock object so that the properties are set correctly. You shouldn't need to do `mockViewContext.Setup(vc => vc.Writer).Returns(textWriter);` either. – David Chandler Dec 21 '15 at 14:03
  • That line `mockDataContainer.Setup(c => c.ViewData).Returns(vd);` should be `mockDataContainer.SetupGet(c => c.ViewData).Returns(vd);` – gfache May 28 '21 at 10:32