13.7 C
London
Wednesday, May 22, 2024

Take a look at-Driving HTML Templates


After a decade or extra the place Single-Web page-Purposes generated by
JavaScript frameworks have
turn into the norm
, we see that server-side rendered HTML is changing into
well-liked once more, additionally because of libraries akin to HTMX or Turbo. Writing a wealthy net UI in a
historically server-side language like Go or Java is not simply attainable,
however a really enticing proposition.

We then face the issue of the right way to write automated checks for the HTML
components of our net purposes. Whereas the JavaScript world has advanced highly effective and refined methods to check the UI,
ranging in measurement from unit-level to integration to end-to-end, in different
languages we would not have such a richness of instruments out there.

When writing an internet utility in Go or Java, HTML is often generated
by way of templates, which include small fragments of logic. It’s actually
attainable to check them not directly by way of end-to-end checks, however these checks
are sluggish and costly.

We are able to as a substitute write unit checks that use CSS selectors to probe the
presence and proper content material of particular HTML parts inside a doc.
Parameterizing these checks makes it straightforward so as to add new checks and to obviously
point out what particulars every take a look at is verifying. This strategy works with any
language that has entry to an HTML parsing library that helps CSS
selectors; examples are supplied in Go and Java.

Motivation

Why test-drive HTML templates? In spite of everything, essentially the most dependable option to examine
{that a} template works is to render it to HTML and open it in a browser,
proper?

There’s some reality on this; unit checks can not show {that a} template
works as anticipated when rendered in a browser, so checking them manually
is important. And if we make a
mistake within the logic of a template, often the template breaks
in an apparent approach, so the error is rapidly noticed.

Alternatively:

  • Counting on guide checks solely is dangerous; what if we make a change that breaks
    a template, and we do not take a look at it as a result of we didn’t suppose it could affect the
    template? We would get an error at runtime!
  • Templates usually include logic, akin to if-then-else’s or iterations over arrays of things,
    and when the array is empty, we regularly want to indicate one thing totally different.
    Handbook checking all instances, for all of those bits of logic, turns into unsustainable in a short time
  • There are errors that aren’t seen within the browser. Browsers are extraordinarily
    tolerant of inconsistencies in HTML, counting on heuristics to repair our damaged
    HTML, however then we would get totally different ends in totally different browsers, on totally different units. It is good
    to examine that the HTML buildings we’re constructing in our templates correspond to
    what we expect.

It seems that test-driving HTML templates is simple; let’s have a look at the right way to
do it in Go and Java. I might be utilizing as a place to begin the TodoMVC
template
, which is a pattern utility used to showcase JavaScript
frameworks.

We’ll see strategies that may be utilized to any programming language and templating expertise, so long as we have now
entry to an acceptable HTML parser.

This text is a bit lengthy; it’s your decision to check out the
remaining resolution in Go or
in Java,
or leap to the conclusions.

Degree 1: checking for sound HTML

The primary factor we wish to examine is that the HTML we produce is
mainly sound. I do not imply to examine that HTML is legitimate based on the
W3C; it could be cool to do it, but it surely’s higher to begin with a lot easier and sooner checks.
For example, we wish our checks to
break if the template generates one thing like

<div>foo</p>

Let’s examine the right way to do it in levels: we begin with the next take a look at that
tries to compile the template. In Go we use the usual html/template bundle.

Go

  func Test_wellFormedHtml(t *testing.T) {
    templ := template.Should(template.ParseFiles("index.tmpl"))
    _ = templ
  }

In Java, we use jmustache
as a result of it is quite simple to make use of; Freemarker or
Velocity are different frequent decisions.

Java

  @Take a look at
  void indexIsSoundHtml() {
      var template = Mustache.compiler().compile(
              new InputStreamReader(
                      getClass().getResourceAsStream("/index.tmpl")));
  }

If we run this take a look at, it’s going to fail, as a result of the index.tmpl file does
not exist. So we create it, with the above damaged HTML. Now the take a look at ought to go.

Then we create a mannequin for the template to make use of. The appliance manages a todo-list, and
we will create a minimal mannequin for demonstration functions.

Go

  func Test_wellFormedHtml(t *testing.T) {
    templ := template.Should(template.ParseFiles("index.tmpl"))
    mannequin := todo.NewList()
    _ = templ
    _ = mannequin
  }

Java

  @Take a look at
  void indexIsSoundHtml() {
      var template = Mustache.compiler().compile(
              new InputStreamReader(
                      getClass().getResourceAsStream("/index.tmpl")));
      var mannequin = new TodoList();
  }

Now we render the template, saving the ends in a bytes buffer (Go) or as a String (Java).

Go

  func Test_wellFormedHtml(t *testing.T) {
    templ := template.Should(template.ParseFiles("index.tmpl"))
    mannequin := todo.NewList()
    var buf bytes.Buffer
    err := templ.Execute(&buf, mannequin)
    if err != nil {
      panic(err)
    }
  }

Java

  @Take a look at
  void indexIsSoundHtml() {
      var template = Mustache.compiler().compile(
              new InputStreamReader(
                      getClass().getResourceAsStream("/index.tmpl")));
      var mannequin = new TodoList();
  
      var html = template.execute(mannequin);
  }

At this level, we wish to parse the HTML and we count on to see an
error, as a result of in our damaged HTML there’s a div aspect that
is closed by a p aspect. There may be an HTML parser within the Go
commonplace library, however it’s too lenient: if we run it on our damaged HTML, we do not get an
error. Fortunately, the Go commonplace library additionally has an XML parser that may be
configured to parse HTML (because of this Stack Overflow reply)

Go

  func Test_wellFormedHtml(t *testing.T) {
    templ := template.Should(template.ParseFiles("index.tmpl"))
    mannequin := todo.NewList()
    
    // render the template right into a buffer
    var buf bytes.Buffer
    err := templ.Execute(&buf, mannequin)
    if err != nil {
      panic(err)
    }
  
    // examine that the template may be parsed as (lenient) XML
    decoder := xml.NewDecoder(bytes.NewReader(buf.Bytes()))
    decoder.Strict = false
    decoder.AutoClose = xml.HTMLAutoClose
    decoder.Entity = xml.HTMLEntity
    for {
      _, err := decoder.Token()
      change err {
      case io.EOF:
        return // We're completed, it is legitimate!
      case nil:
        // do nothing
      default:
        t.Fatalf("Error parsing html: %s", err)
      }
    }
  }

supply

This code configures the HTML parser to have the suitable degree of leniency
for HTML, after which parses the HTML token by token. Certainly, we see the error
message we wished:

--- FAIL: Test_wellFormedHtml (0.00s)
    index_template_test.go:61: Error parsing html: XML syntax error on line 4: surprising finish aspect </p>

In Java, a flexible library to make use of is jsoup:

Java

  @Take a look at
  void indexIsSoundHtml() {
      var template = Mustache.compiler().compile(
              new InputStreamReader(
                      getClass().getResourceAsStream("/index.tmpl")));
      var mannequin = new TodoList();
  
      var html = template.execute(mannequin);
  
      var parser = Parser.htmlParser().setTrackErrors(10);
      Jsoup.parse(html, "", parser);
      assertThat(parser.getErrors()).isEmpty();
  }

supply

And we see it fail:

java.lang.AssertionError: 
Anticipating empty however was:<[<1:13>: Unexpected EndTag token [</p>] when in state [InBody],

Success! Now if we copy over the contents of the TodoMVC
template
to our index.tmpl file, the take a look at passes.

The take a look at, nevertheless, is simply too verbose: we extract two helper capabilities, in
order to make the intention of the take a look at clearer, and we get

Go

  func Test_wellFormedHtml(t *testing.T) {
    mannequin := todo.NewList()
  
    buf := renderTemplate("index.tmpl", mannequin)
  
    assertWellFormedHtml(t, buf)
  }

supply

Java

  @Take a look at
  void indexIsSoundHtml() {
      var mannequin = new TodoList();
  
      var html = renderTemplate("/index.tmpl", mannequin);
  
      assertSoundHtml(html);
  }

supply

We’re releasing this text in installments. Future installments
will transcend easy validity and
clarify the right way to take a look at the content material of the generated HTML.

To search out out after we publish the subsequent installment subscribe to this
web site’s
RSS feed, or Martin’s feeds on
Mastodon,
LinkedIn, or
X (Twitter).




Latest news
Related news

LEAVE A REPLY

Please enter your comment!
Please enter your name here