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.

1
  • Eventually it generates a string represents the html layout. You can test it by comparing the string result. Commented Jul 11, 2016 at 14:15

2 Answers 2

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?

Sign up to request clarification or add additional context in comments.

2 Comments

What I would like to avoid is using string literals like yours "<label class=\"control-label col-md-2\" for=\"Id\">Id<span class=\"required\">*</span></label>". My point is to find a way how to e.g. parse generated HTML into C# structures and test just particular parts.
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.
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.

4 Comments

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.
@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.
@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?
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) 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.