0

Is there any sophisticated way how to unit test the results of HtmlHelpers? For example I have a helper that generates html markup for my custom inputs (I am using TagBuilder inside). The result is IHtmlString which I have to convert to string and compare it with expected string in unit tests. But this becomes very complicated, since in html I dont care about attributes order, I have to escape expected strings etc. Any ideas how to test it more cleaner?


SOLUTION: Based on comments and answers bellow I have started to write unit tests using HtmlAglityPack. Code looks like this:

var states = new[] { MultiStateInputState.Unknown, MultiStateInputState.Yes, MultiStateInputState.No };

var actual = Html.Abb().MultiStateInput(states).Name("myinput").ToHtmlString();
var doc = new HtmlDocument();
var actualTextInput = doc.DocumentNode.ChildNodes.First(n => n.Name == "input");

 Assert.That(node, Is.Not.Null);
 Assert.That(node.Attributes, Is.Not.Null);
 Assert.That(node.Attributes, Is.Not.Empty);
 var attribute = node.Attributes.Single(a => a.Name == "name");
 Assert.That(attribute, Is.Not.Null);
 Assert.That(attribute.Value, Is.EqualTo("myinput"));

This is much more better than comparing two strings. No need to take care about attribute order and other stuff.

Ondra Netočný
  • 410
  • 3
  • 16

2 Answers2

1

I answered this question which shows my unit tests for testing HtmlHelpers.

Here's the code for the testing

public static class LabelExtensionFixtures
{
    [TestFixture]
    public class should_return_label_with_required_info : MvcExtensionFixtureBase
    {
        private class TestClass
        {
            [Required]
            public Guid Id { get; set; }
        }

        private MvcHtmlString _expectedResult;
        private HtmlHelper<TestClass> _sut;
        private MvcHtmlString _result;

        [SetUp]
        public void Given()
        {
            //arrange
            _expectedResult =
                MvcHtmlString.Create(
                    "<label class=\"control-label col-md-2\" for=\"Id\">Id<span class=\"required\">*</span></label>");
            _sut = CreateHtmlHelper(new TestClass {Id = Guid.NewGuid()});

            //act
            _result = _sut.LabelFor(model => model.Id, new { @class = "control-label col-md-2" }, "*");
        }

        [Test]
        public void Test()
        {
            //asert
            Assert.That(_result.ToHtmlString(), Is.EqualTo(_expectedResult.ToHtmlString()));
        }
    }
}

    public abstract class MvcExtensionFixtureBase
    {
        protected HtmlHelper<T> CreateHtmlHelper<T>(T instance)
        {
            var viewDataDictionary = new ViewDataDictionary<T>(instance);
            var viewContext = A.Fake<ViewContext>();
            A.CallTo(() => viewContext.ViewData).Returns(viewDataDictionary);

            var viewDataContainer = A.Fake<IViewDataContainer>();
            A.CallTo(() => viewDataContainer.ViewData).Returns(viewDataDictionary);

            return new HtmlHelper<T>(viewContext, viewDataContainer);
        }
    }

I'm using FakeItEasy for the fakes.

What issues are you seeing when you testing? Maybe post your code?

Community
  • 1
  • 1
Fran
  • 6,440
  • 1
  • 23
  • 35
  • What I would like to avoid is using string literals like yours `""`. My point is to find a way how to e.g. parse generated HTML into C# structures and test just particular parts. – Ondra Netočný Jul 12 '16 at 06:05
  • I agree with Chris's comment. But if you are set on parsing l, you could probably use HtmlAgilityPack to parse any XML fragments. It has a very forgiving HTML parser. – Fran Jul 12 '16 at 10:51
1

One way to test it is to create a transformation in the other way too, i.e. from html to some C# representation of your control. This allows you to re-parse the generated HTML and verify things against the C# objects - and then, of course, attribute orders etc doesn't matter anymore.

A sample test might look like this:

[Fact]
public void ControlIsGeneratedCorrectly()
{
    var expected = new CustomControl { Foo = "Foo", Bar = 16 };

    var parser = new CustomControlParser();
    var model = new SomeViewModel { Foo = "Foo" };

    var actual = parser.Parse(Html.InputFor(model));

    Assert.AreEqual(expected.Foo, actual.Foo);
    Assert.AreEqual(expected.Bar, actual.Bar);
}

Of course, this adds complication to your tests since the parser is probably non-trivial. But that one is easier to test; just give it a couple of HTML samples and make sure the returned C# objects show the correct properties. If you find a case where the parser breaks the tests of your html helper, then you probably need to add a few test cases to the parser and fix those too.

Tomas Aschan
  • 58,548
  • 56
  • 243
  • 402
  • The parser just adds extra complexity, and doesn't really satisfy the test. If you're testing an HTML helper, the part you're interested in is what's returned. So, `expected` should just be the HTML you would expect to return given the test model, and `actual` would be the actual HTML returned when passing that test model. – Chris Pratt Jul 11 '16 at 14:39
  • @ChrisPratt: Sure, but that test is very difficult to specify correctly given that e.g. attribute order etc is not important. Going via a parser like this gives additional value in that it tests some *properties* of the generated HTML (namely, the ones that can be expressed by the C# object we parse it back into) without requiring more of the HTML helper than necesary. – Tomas Aschan Jul 11 '16 at 15:19
  • @TomasLycken thanks, this is what I am looking for. Do you have any particular examples of parsers? Is there a recommended way how transform HTML string into C# objects? – Ondra Netočný Jul 12 '16 at 06:09
  • 1
    Since HTML is XML (or, at least, it won't hurt you if yours is), I'd just use an existing XML parser (e.g. [this one](https://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractserializer(v=vs.110).aspx)) and parse the generated HTML as XML. Your C# representation will have to be annotated with some attributes, but that's relatively simple, and the class exists for this sole purpose anyway so they won't hurt anything else. – Tomas Aschan Jul 12 '16 at 06:31