【问题标题】:Unit testing generic htmlHelper methods with nunit使用 nunit 对通用 htmlHelper 方法进行单元测试
【发布时间】:2023-12-12 12:57:01
【问题描述】:

我是 nUnit 的新手,我的任务是为一些 htmlhelper 扩展方法创建单元测试。

我应该如何为以下方法创建单元测试?

    public static MvcHtmlString EnumDropDownListForOrderBy<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, bool orderById, string firstElement = null, object htmlAttributes = null)
    {
        ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        Type enumType = GetNonNullableModelType(metadata);
        IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();

        IEnumerable<SelectListItem> items = values.Select(value => new SelectListItem()
        {
            Text = value.GetAttributeFrom<DescriptionAttribute>(value.ToString()).Description,
            Value = value.ToString(),
            Selected = value.Equals(metadata.Model)
        });

        IEnumerable<SelectListItem> itemsFiltered = items.Where(e => !string.IsNullOrEmpty(e.Text)).AsEnumerable();

        itemsFiltered = itemsFiltered.OrderBy(e => (orderById ? e.Text : e.Value));

        return htmlHelper.DropDownListFor(
            expression,
            itemsFiltered,
            firstElement,
            htmlAttributes
        );
    }

任何帮助将不胜感激

【问题讨论】:

  • 你想测试什么具体的行为?
  • 也许只是一个简单的测试,看看是否可以返回 null?
  • 使用 value.GetAttributeFrom(value.ToString()).Description,value 是一个泛型,即 TEnum。 GetAttributeFrom 是一种扩展方法,您指定要用作 的通用类型,您在扩展方法中指定 TEnum。你能编译这段代码吗?如果您可以发布您在问题中使用的私有方法和任何扩展方法,那就太好了。这样可以清楚地了解需要存根/模拟的内容等。
  • 嗨 Spock,这段代码可以编译。 GetAttributeFrom 的内容为: public static class ExtensionMethods { public static T GetAttributeFrom(this object instance, string propertyName) where T : Attribute; 来自命名空间 System.ComponentModel.DescriptionAttribute
  • 谢谢@MohandMokri - 请看下面的答案。

标签: unit-testing model-view-controller nunit html-helper


【解决方案1】:

以下是为此编写单元测试的方法。请注意,由于您没有指定使用 Mock 对象框架,因此我将使用穷人技术,即 hand written stubs and mocks。如果您使用Moq,还有另一种辅助方法。

请务必注意,为了简化代码执行,我对您的扩展方法进行了一些更改,因此测试不会意外失败。无论如何,检查任何意外的行为者都是一种很好的防御性编程实践。

回到测试。

SUT(被测系统)

这就是 SUT(被测系统)和支持类型的样子。 (请随时根据您的需要进行修改)

public static class MyHtmlHelper
{
    public static MvcHtmlString EnumDropDownListForOrderBy<TModel, TEnum>
         (this HtmlHelper<TModel> htmlHelper, 
        Expression<Func<TModel, TEnum>> expression,
        bool orderById, string firstElement = null, object htmlAttributes = null, 
        Func<ModelMetadata> fromLambFunc = null)
    {
        ModelMetadata metadata =
        ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);    
        Type enumType = GetNonNullableModelType(metadata);
        IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();

        IEnumerable<SelectListItem> items = 
            values.Select(value => new SelectListItem()
        {
            Text = GetText(value),
            Value = value.ToString(),
            Selected = value.Equals(metadata.Model)
        });

        IEnumerable<SelectListItem> itemsFiltered = 
        items.Where(e => !string.IsNullOrEmpty(e.Text)).AsEnumerable();
        itemsFiltered = itemsFiltered.OrderBy(e => (orderById ? e.Text : e.Value));

        return htmlHelper.DropDownListFor
        (expression, itemsFiltered, firstElement, htmlAttributes);
    }

    private static Type GetNonNullableModelType(ModelMetadata metadata) {
        return typeof (SomeEnum);
    }

    private static string GetText<TEnum>(TEnum value) {
        return value.GetAttributeFrom<DescriptionAttribute>(value.ToString()) != null
            ? value.GetAttributeFrom<DescriptionAttribute>(value.ToString()).Description
            : string.Empty;
    }
}

public static class ExtensionMethodsAttr
{
    public static T GetAttributeFrom<T>(this object instance, string propertyName) 
      where T : Attribute
    {
        var attrType = typeof(T);
        var property = instance.GetType().GetProperty(propertyName);

        return property != null ?
        (T)property.GetCustomAttributes(attrType, false).First() : default(T) ;
    }
}

public enum SomeEnum { A,}

单元测试

[TestFixture]
public class HtmlHelperTests
{
    [Test]
    public void EnumDropDownListForOrderBy_InvokeDropDownListFor_ReturnsExpectedSelectItemResult()
    {
        //Arrange
        var expected = "<select id=\"Foo\" name=\"Foo\"></select>";
        var fakeHtmlHelper = CreateHtmlHelperStaticStubs
        (new ViewDataDictionary(new FakeViewModel() {Foo = SomeEnum.A}));
        //var fakeHtmlHelper = CreateHtmlHelperUsingMoq
        (new ViewDataDictionary(new FakeViewModel(){Foo = SomeEnum.A}));

        //Act
        var result = fakeHtmlHelper.EnumDropDownListForOrderBy
        (model => model.Foo, It.IsAny<bool>(), null, null, null);

        //Assert
        Assert.AreEqual(expected, result.ToString());
    }


    private static HtmlHelper<FakeViewModel> 
         CreateHtmlHelperStaticStubs(ViewDataDictionary viewData)
    {
        var stubControllerContext = new ControllerContext(new FakeHttpContext(), new RouteData(), new FakeController());

        var stubViewContext = new ViewContext(stubControllerContext, new FakeView(),
            new ViewDataDictionary(new FakeViewModel() { Foo = SomeEnum.A }),
            new TempDataDictionary(), new TextMessageWriter());

        var fakeViewDataContainer = new FakeViewDataContainer();
        fakeViewDataContainer.ViewData = viewData;

        return new HtmlHelper<FakeViewModel>(stubViewContext, fakeViewDataContainer);
    }

    //Moq version
    private static HtmlHelper<FakeViewModel> 
       CreateHtmlHelperUsingMoq(ViewDataDictionary viewData)
    {
        var stubControllerContext = new Mock<ControllerContext>();
        stubControllerContext.Setup(x => x.HttpContext).Returns(new Mock<HttpContextBase>().Object);
        stubControllerContext.Setup(x => x.RouteData).Returns(new RouteData());
        stubControllerContext.Setup(x => x.Controller).Returns(new Mock<ControllerBase>().Object); ;

        var stubViewContext = new Mock<ViewContext>();
        stubViewContext.Setup(x => x.View).Returns(new Mock<IView>().Object);
        stubViewContext.Setup(x => x.ViewData).Returns(viewData);
        stubViewContext.Setup(x => x.TempData).Returns(new TempDataDictionary());

        var mockViewDataContainer = new Mock<IViewDataContainer>();

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

        return new HtmlHelper<FakeViewModel>(stubViewContext.Object, mockViewDataContainer.Object);
    }
}   


class FakeHttpContext : HttpContextBase
{
    private Dictionary<object, object> _items = new Dictionary<object, object>();
    public override IDictionary Items { get { return _items; } }
}

class FakeViewDataContainer : IViewDataContainer
{
    private ViewDataDictionary _viewData = new ViewDataDictionary();
    public ViewDataDictionary ViewData { get { return _viewData; } set { _viewData = value; } }
}

class FakeController : Controller { }

class FakeView : IView
{
    public void Render(ViewContext viewContext, System.IO.TextWriter writer)
    {
        throw new NotImplementedException();
    }
}

public class FakeViewModel {
    public SomeEnum Foo { get; set; }
}

【讨论】:

  • 嗨,Spock,太好了!非常感谢您,也感谢您提供使用最小起订量的替代方案。这非常有用,我相信其他人会发现它。
最近更新 更多