单元测试 HtmlHelper 扩展方法失败

12

我正在尝试测试我编写的一些HtmlHelper扩展方法。我的第一个问题是如何创建一个HtmlHelper实例,但我使用了以下代码解决了这个问题:

private static HtmlHelper<T> CreateHtmlHelper<T>(T model)
{
    var viewDataDictionary = new ViewDataDictionary(model);
    var controllerContext = new ControllerContext(new Mock<HttpContextBase>().Object,
    new RouteData(),
    new Mock<ControllerBase>().Object);

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

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

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

我的几个测试现在都很好,但有一个测试会抛出异常。该测试定义如下:

// Arrange
var inputDictionary = CreateDictionary();
var htmlHelper = CreateHtmlHelper(inputDictionary);

// Act
var actualHtmlString = htmlHelper.EditorFor(m => m.Dict, model).ToHtmlString();
...
< p > EditorFor 方法是我的扩展方法。在该方法的某处,调用了以下内容:

tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(expression, metadata));    

当我从我的单元测试中执行这段代码时,会抛出以下异常:
System.NullReferenceExceptionObject reference not set to an instance of an object.
   at System.Web.Mvc.ViewContext.ScopeCache.Get(IDictionary`2 scope, HttpContextBase httpContext)
   at System.Web.Mvc.ViewContext.get_UnobtrusiveJavaScriptEnabled()
   at System.Web.Mvc.HtmlHelper.GetUnobtrusiveValidationAttributes(String name, ModelMetadata metadata)
   at AspNetMvcDictionarySerialization.HtmlHelperExtensions.InputTagHelper(HtmlHelper htmlHelper, ModelMetadata metadata, InputType inputType, String expression, IDictionary`2 htmlAttributes, String fullName, Int32 index, String fieldType, String val) in HtmlHelperExtensions.cs: line 154

因此,代码在ScopeCache.Get中失败了,但是为什么?有人有解决方法吗?

2个回答

24

我最终做的是查看ASP.NET MVC源代码。在他们的代码中,他们也测试HtmlHelper实例。他们使用一个名为MvcHelper的实用程序类来执行此操作,该类提供方便的方法来创建具有正确准备的HTTP上下文的新HtmlHelper实例。

删除不需要的代码后,我最终得到了以下类:

public static class MvcHelper
{
    public static HtmlHelper<TModel> GetHtmlHelper<TModel>(TModel inputDictionary)
    {
        var viewData = new ViewDataDictionary<TModel>(inputDictionary);
        var mockViewContext = new Mock<ViewContext> { CallBase = true };
        mockViewContext.Setup(c => c.ViewData).Returns(viewData);
        mockViewContext.Setup(c => c.HttpContext.Items).Returns(new Hashtable());

        return new HtmlHelper<TModel>(mockViewContext.Object, GetViewDataContainer(viewData));
    }

    public static IViewDataContainer GetViewDataContainer(ViewDataDictionary viewData)
    {
        var mockContainer = new Mock<IViewDataContainer>();
        mockContainer.Setup(c => c.ViewData).Returns(viewData);
        return mockContainer.Object;
    }
}

使用这个辅助类,我的代码成功地执行了。
我已经创建了一个gist来包含完整的辅助类,以便于在你的项目中轻松引用:https://gist.github.com/ErikSchierboom/6da474dcd5751fbbc94c

3
哥们儿,你太棒了。这是第一个有效的方法,它使我可以测试一些辅助方法。我需要找到其他几个关于模拟HtmlHelper的StackOverflow问题,并说这是更好的答案。 - Nicholas Petersen
有人在那段代码中遇到了麻烦吗?我最终遇到了CachedAssociatedMetadataProvider.PrototypeCache抛出异常 :/ - SOReader
1
非常感谢。在我找到你的帖子之前,我花了整整一天时间解决“NullReferenceException”问题。 - Ben Rubin
模拟HttpContext.Items也有助于解决“方法或操作未实现”的异常问题。 - Volodymyr Kotylo

1
看起来你可能也需要模拟HttpContext。

你是指 HttpContext.Current 吗?你有任何示例代码来做这件事吗? - Erik Schierboom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接