I encountered a defect arising from a Freemarker template, fixed it, and then wanted a better way of testing it than firing up the app and checking the effects visually in a browser. Ignoring for a minute whether or not a Freemarker template should ever have anything in it that needs unit testing, let’s see how to do it if we want to.
Code for this is available in GitHub at https://github.com/Todderz/freemarker-unit-test
Feel free to copy and do what you want with it.
To unit test a Freemarker template you need to configure Freemarker, load the template, process the template against some model, get hold of the output as text or HTML, and make assertions against it.
That’s a lot of engineering that isn’t relevant to the test, so let’s hide it with a JUnit Rule. Now we can specify a JUnit Rule that will load a given template, eg:
@Rule public FreemarkerTestRule template = new FreemarkerTestRule("src/test/java", "sample.ftl");
And in our test we can get hold of an XML representation of the output like this:
Source theXml = template.xmlResponseFor(model);
Finally, we can make assertions against the XMl, using Hamcrest XML matchers, like so:
assertThat(theXml, hasXPath("//td[@id='this-one']", containsString("Expected")));
The test case can now focus on setting up the model for this scenario, and asserting against the output, with all the engineering work out of sight. Using Hamcrest XML matchers means that your test can be reasonably well-protected from future changes to styling and layout aspects of the output, homing in on specific elements with whatever your XPath skills are. This is important because we don’t want our unit tests to break if page layout changes.
Unit tests? Well, OK, no, not really. They are after all loading Freemarker template files, but close enough. Loading the templates and then processing them may be noticeably slow though, so these tests should either be marked as integration tests or grouped with slow tests so that they don’t cause your commit build to get slower.
The JUnit Rule will also let you get hold of the template output as an HtmlUnit HtmlPage, so that you can make use of all the support HtmlUnit offers for testing HTML.
An interesting bonus is that an HtmlPage has an asText() method, which gives you a text representation of what the HTML would look like. This is pretty cool. Eg for some HTML like this
<table id="exam-dates-table"> <thead> <tr class="odd"> <th>Day</th> <th>Date</th> <th>Extra Info</th> </tr> </thead> <tbody> <tr class='disable'> <td> <input type="radio" value="2014/06/01" disabled> Sunday </td> <td>Jun 01, 2014</td> <td>'Extra Fee Charged'</td> </tr> <tr class='enable selected'> <td> <input type="radio" value="2014/06/02" checked="checked" > Monday </td> <td>Jun 02, 2014</td> <td>'Extra Fee Charged'</td> </tr> <tr class='enable'> <td> <input type="radio" value="2014/06/03" > Tuesday </td> <td>Jun 03, 2014</td> <td></td> </tr> </tbody> </table>
You’d see a text representation like this:
Day Date Extra Info unchecked Sunday Jun 01, 2014 'Extra Fee Charged' checked Monday Jun 02, 2014 'Extra Fee Charged' unchecked Tuesday Jun 03, 2014
Being able to do that while you’re developing, without having to fire up the app and poke it through browsers, is potentially very useful.
One thing this sort of testing will do is force you to decompose your FTLs into smaller independent macros, because to execute a test against one requires constructing every aspect of the model that your FTL expects to have available. A smaller macro means a smaller model, which means you can write tests without loads of unnecessary faff.
So, is it ever a good thing to be testing your Freemarker templates?
Well, if you have complex logic that is very clearly related to the presentation, then yes it probably is. The thing is to be sure that your logic really is all about presentation and wouldn’t be better pulled down into code. Pulling the business rules into code and adding suitable presentation-agnostic attributes to your model, is likely to mean that your presentation logic loses any complexity that would require testing.
If you find that you do have complexity and would rather test it in isolation, automatically, then this approach will help.
If you want to unit test Freemarker templates, then use this JUnit Rule and a bit of Hamcrest, and your test cases will be a few expressive lines that focus just on the test scenario, with all the engineering work of loading and processing templates and converting output won’t cause any distraction to the readers of your tests.
With XPath, your tests can be reasonably resistant to unimportant changes to the HTML.
The fact that it is likely to encourage a clearer decomposition of templates into macros etc is also a good thing.
Get the working code here https://github.com/Todderz/freemarker-unit-test
NB I should really have made this, at least the template loading part of it, a ClassRule rather than a TestRule. “Exercise for the reader”…