So If You’re Using Tag Libraries for Your View Models You Have To Test Them, Right?

Datetime:2016-08-23 02:49:54          Topic: HTML  Unit Testing           Share

In a previous post I introduced thinking about (visual) components and used a Task Browser as an example of an user interface “component” .

I explained that using

  • View Model(s) e.g. plain-old Groovy objects (POGOs) holding the related data e.g. a  class TaskBrowser
  • Tag Libraries ( LayoutTagLib ) and tags ( def taskBrowser ) to render the associated HTML ( views/layouts/components/_taskBrowser.gsp ) to the page

allows for more maintainable and testable code.

Let’s put our money where our mouth is and see how one could test the used tag library .

The Parts

So these are the (simplified) parts in the equation.

Task– The domain class

class Task {
  enum Type { PERSONAL, WORK }
 
  String title
  Type type
}

TaskBrowser– Just a POGO with the data

class TaskBrowser {
 
  List tasks = []
 
  /**
   * Month to start with.
   *
   * @return number between 1 and 12
   */
  int getStartMonth() {
    def nowDate = new Date()
    nowDate[Calendar.MONTH] + 1
  }
}

HomeController– Creating the Task Browser in an  index action.

class HomeController {
  def taskService
 
  def index() {
    [taskBrowser: new TaskBrowser(tasks: taskService.retrieveTasks())]
  }
}

home/index.gsp– The GSP for the index action

<!doctype html>
<html>
 <head>
 <meta name="layout" content="main" />
 <title>Tasks</title>
 </head>
 
 <body>
 <g:taskBrowser taskBrowser="${taskBrowser}"/>
 </body>
</html>

views/layouts/components/_taskBrowser.gsp– The Task Browser

HTML

<div class="row month-${startMonth}" id="task-browser">

<div class="six columns">
  <g:if test="${tasks}">
  <%-- 500 lines more... --%>

LayoutTagLib– Finally, the tag library

/**
 * Renders the task browser.
 * 
 * @attr taskBrowser REQUIRED a task browser instance
 * @attr filter Optionally a {@link Task.Type} to show only those tasks
 */
def taskBrowser = { attrs ->
  if (!attrs.taskBrowser) {
    throwTagError("Tag [taskBrowser] is missing " +
                    "required attribute [taskBrowser]")
  }
 
  TaskBrowser browser = attrs.taskBrowser
 
  // filter tasks by type
  def tasks = browser.tasks
  if (attrs.filter && attrs.filter instanceof Task.Type) {
    tasks = browser.tasks.findAll { task -> task.type == attrs.filter }
  } 
 
  out << render(template: '/layouts/components/taskBrowser', 
    model: [tasks : tasks,
      months : browser.months,
      startMonth : browser.startMonth
  ])
}

Unit Test

If you look at the tag library code there’s a few things interesting enough to cover with an unit test.

Normally whenever you use a Grails command to create a controller, service or tag library, also an associated unit test is created. If not, you can always create one later on with

In any case, we start with a LayoutTagLibSpec which is initially pretty empty.

import grails.test.mixin.TestFor
import spock.lang.Specification
 
@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {
 
 def setup() {
 }
 
 def cleanup() {
 }
 
 void "test something"() {
 expect:"fix me"
 true == false
 }
}

The @TestFor  annotation is part of the Grails framework. It not only indicates the class-under-test (aka the actual unit we’re supposed to be testing here), but also gives us a concrete instance of that class.

More on that later.

Now we can implement our first test, called…

Task browser tag should show all tasks by default

Although the skeleton testmethod "test something" starts with “test…”, I try to omit that part. We’re a creating tests obviously and repeating “test xxx” up front has no additional value but taking up space.

If we were in the unit test of the TaskBrowser (e.g. TaskBrowserSpec ) I would skip the name of the class-under-test from the testmethod e.g. “

task browser tag should show…”. Since we’re in a more generic LayoutTagLib I would like to know which tag – of many more to come ofcourse – we’re talking about, so I do

start with “task browser tag…”

I usually start by placing my Given/When/Then Spock labels in the test method. This helps me structure my own head in thinking about

1. What are the prequisites? (Given)

2. What’s the actual code to invoke? (When)

3. What’s there to assert or verify? (Then)

Here’s what I have now:

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {
 
 void "task browser tag should show all tasks by default"() {
 
   given:
 
   when:
 
   then:
 }
}

The actual invocation of the tag goes under When .

Since the @TestFor annotation points to a tag library class, we’re given an implicit tagLib variable to work which references a clean LayoutTagLib instance each time.

Don’t do this in a unit test, because it’s the preTestFor way:

def layoutTagLib = new LayoutTagLib()
// or <span class="pl-k">def</span> layoutTagLib <span class="pl-k">=</span> applicationContext<span class="pl-k">.</span>getBean(Layout<span class="pl-k">TagLib</span>)
layoutTagLib.taskBrowser(...)

but use the tagLib , Luke.

tagLib.taskBrowser(...)

So we have this:

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {
 
 void "task browser tag should show all tasks by default"() {
 
   given:
 
   when:
   tagLib.taskBrowser()
 
   then:
 }
}

I know that the happy-path flow for this test needs a TaskBrowser instance. There should be al least one task to verify the tag should show it by default. So let’s add them:

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {
 
 void "task browser tag should show all tasks by default"() {
 given:
 def task = new Task(title: "My task")
 def browser = new TaskBrowser(tasks: [task])
 
 when:
 tagLib.taskBrowser(taskBrowser: browser)
 
 then:
 true
 }
}

Hey, why is true there in the Then block? It’s because we need to have a Then block after When , to be able to execute this test once at this moment. Normally we would write this probably with Expect as

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {
 
 void "task browser tag should show all tasks by default"() {
 
   given:
 
   expect:
 
 }
}

but I’m too lazy to update it to Expect and change it back later to When/Then :wink: We need the Then later on anyway.

What Grails does in a unit test for a tag library is actually rendering the template /layouts/components/_taskBrowser.gsp using the supplied model.

Remember the code for the LayoutTagLib ?

/**
 * Renders the task browser.
 * 
 * @attr taskBrowser REQUIRED a task browser instance
 * @attr filter Optionally a {@link Task.Type} to show only those tasks
 */
def taskBrowser = { attrs ->
  if (!attrs.taskBrowser) {
    throwTagError("Tag [taskBrowser] is missing " +
                    "required attribute [taskBrowser]")
  }
 
  TaskBrowser browser = attrs.taskBrowser
 
  // filter tasks by type
  def tasks = browser.tasks
  if (attrs.filter && attrs.filter instanceof Task.Type) {
    tasks = browser.tasks.findAll { task -> task.type == attrs.filter }
  } 
 
  out << render(template: '/layouts/components/taskBrowser', 
    model: [tasks : tasks,
      months : browser.months,
      startMonth : browser.startMonth
  ])
}

If above (simplistic) test succeeds, the _taskBrowser.gsp — and its logic — hasn’t failed with an exception. You can make a typo in your template(s) and see the evaluation fail. Covering just the evaluation of a GSP might be worth a test, but we haven’t checked anything of the contents.

How do we know if the correct template is referenced? How do we know if the correct model is actually passed along?

The Inevitable Truth

If you look at the Testing chapter of the Grails documentation, you’ll see a simplistic example of testing the response of a SimpleTagLib

class SimpleTagLib {
  static namespace = 's'
 
  def hello = { attrs, body ->
    out << "Hello ${attrs.name ?: 'World'}"
  }
@TestFor(SimpleTagLib)
class SimpleTagLibSpec extends Specification {
 
  void "test tag calls"() {
    expect:
    tagLib.hello().toString() == 'Hello World'

Our tag isn’t as simple as returning “Hello World” – our tag renders 500 lines of Sophisticated Task Browser HTML to the output buffer. Verifying this the simple way isn’t that simple.

There are a few approaches here.

#1 – Checking for parts

Often contains is used in pieces of code which simply need to be checked for the presence of an item. We could try to verify the existence of our single task “My task” somewhere in the output like this:

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {
 
 void "task browser tag should show all tasks by default"() {
   given:
   def task = new Task(title: "My task")
   def browser = new TaskBrowser(tasks: [task])
 
   when:
   def result = tagLib.taskBrowser(taskBrowser: browser).toString()
 
   then:
   result.contains "My task"
 }
}

We have verified successfully that “My task” is visible somewhere in 500 lines of output.

(Psst, quickly beef up the test before we continue – make sure we always have at least a multiple amount of test items to check logic which deals with a collection, instead of a single item)

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {
 
 void "task browser tag should show all tasks by default"() {
   given:
   def task1 = new Task(title: "My 1st task")
   def task2 = new Task(title: "My 2nd task")
   def browser = new TaskBrowser(tasks: [task1, task2])
 
   when:
   def result = tagLib.taskBrowser(taskBrowser: browser).toString()
 
   then:
   result.contains "My 1st task"
   result.contains "My 2nd task"
 }
}

The disadvantage is that we’ve coupled our test for the tag lib logic (showing or filtering tasks) with the rendering of the HTML (presence of the title of a task)

To alleviate this a bit we should be…

#2 – Controlling what parts are rendered

Just as with Controller tests we can use a feature of the ControllerUnitTestMixin to mock the view used for rendering.

Use the implicit getViews() or getGroovyPages() — which return a Map for us to manipulate. Overwrite the real template with custom content of our own, in which we control what & how the model is rendered.

First make sure we actually overwrite the correct template path, by letting the test fail. The taskBrowser tag says render(template:
'/layouts/components/taskBrowser'... so we have to put alternative contents under key '/layouts/components/_taskBrowser.gsp' — there’s a discrepancy in the format of writing the template path.

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {
 
 void "task browser tag should show all tasks by default"() {
   given:
   def task1 = new Task(title: "My 1st task")
   def task2 = new Task(title: "My 2nd task")
   def browser = new TaskBrowser(tasks: [task1, task2])
 
   when:
   views['/layouts/components/_taskBrowser.gsp'] = 'bogus'
   def result = tagLib.taskBrowser(taskBrowser: browser).toString()
 
   then:
   result.contains "My 1st task"
   result.contains "My 2nd task"
 }
}

This fails correctly…

<h1> INSERT Picture when i can </h1>

…so we know we have the correct key.

Now choose proper contents.

Just printing the collection of tasks should give us all we need to verify that our 2 tasks have been passed to the model to our custom template now.

<h1> INSERT Picture when i can </h1>

Dang!

We can (and should) not rely on the String -representation of a Task class, our model.





About List