- Make a unit test class for the macro.
- Make FreeMarker interprete the macro.
- Confirm that the expected html elements like divs and anchors were present.
Getting the necessary libraries
All I needed was : JUnit, FreeMarker and HtmlUnit. HtmlUnit has quite a lot of dependencies, but all the necessary libraries are part of the HtmlUnit archive. You can easy go to their respective homepage and download them yourself, or get them via Maven. Here are the necessary dependencies for the sample we'll make in this tutorial :<dependencies> <dependency> <groupId>net.sourceforge.htmlunit</groupId> <artifactId>htmlunit</artifactId> <version>2.8</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.16</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> </dependency> </dependencies>
A basic macro
Let's make a basic pagination macro. We'll keep it simple. The page links won't point anywhere. We just want to show page numbers. The current page will be plain text, other page numbers will be html anchors. The macro parameters will be the total number of pages and the current page. It will also show previous and next if necessary.<#macro doPagination totalPages currentPage > <#if (totalPages > 1)> <div id="pagination"> <#-- Previous page --> <#if (currentPage > 1)> <a href="#">Prev</a> </#if> <#-- Page number --> <#list 1 .. totalPages as pageNumber> <#if (pageNumber == currentPage)> <div id="currentPage">${pageNumber}</div> <#else> <div><a href="#">${pageNumber}</a></div> </#if> </#list> <#-- Next page --> <#if (currentPage < totalPages)> <a href="#">Next</a> </#if> </div> </#if> </#macro>
Using a dummy template
We need a FreeMarker template to use the pagination macro. This template will just import the file where the macro is, and call the macro.<#import "/main/webapp/templates/macros/pagination.ftl" as pagination/> <html> <body> <@pagination.doPagination totalPages currentPage /> </body> </html>
Preparing FreeMarker for the tests
We'll use the FreeMarker API to convert the dummy template into html. We'll make use of the following two classes : freemarker.template.Configuration and freemarker.template.Template. Let's set this up in the @Before method of our test class.public class PaginationTest { /** Root directory where the FreeMarker templates are */ private static final String TEMPLATE_ROOT = "src"; /** Dummy pagination template location */ private static final String PAGINATION_TEMPLATE = "test/java/com/kuriqoo/templates/macros/pagination_test.ftl"; private Configuration cfg; private Template template; private Map<String, Object> rootMap; @Before public void setUp() throws Exception { cfg = new Configuration(); cfg.setDirectoryForTemplateLoading(new File(TEMPLATE_ROOT)); template = cfg.getTemplate(PAGINATION_TEMPLATE); rootMap = new HashMap<String, Object>(); } }First, the TEMPLATE_ROOT. The macro is in "/src/main/webapp/templates/macros/pagination.ftl" and the dummy template is in "/src/test/java/com/kuriqoo/templates/macros/pagination_test.ftl". So the common template root is "src".
Then, the PAGINATION_TEMPLATE. This points to the dummy template. Note that the root ("src") is not included in it.
We create a Configuration and set its template root directory. Using that Configuration instance, we can build some templates, by calling its getTemplate method. Here, we are making our dummy template. Note that this template is not yet trnasformed into html. This will come after.
The HashMap call rootMap is used by FreeMarker when it generates the html code from the template. This map must contain all the necessary information used by our macro, like the total number of pages and the current page. This information will be set in the test methods.
Unit testing
Each test will be executed with the following steps:- Set the information used by the pagination macro in the root map.
- Make FreeMarker generate html from our template.
- Feed HtmlUnit with our html to generate an HtmlPage instance.
- Declare some expectations, like the expected anchor tags representing page numbers.
- Get the anchor tags present in the pagination div and compare them to the expected ones.
- Assert that the current page is present and is not a link.
Set the information used by the pagination macro in the root map
Our simple macro only need to know the number of pages and the current page, so we'll set that information in the root map used by FreeMarker :private void setPaginationAttributes(int totalPages, int currentPage) { rootMap.put("totalPages", totalPages); rootMap.put("currentPage", currentPage); }
Make FreeMarker generate html from our template and feed HtmlUnit with our html to generate an HtmlPage instance
This is the interesting part. Remember the Template instance we created above ? We'll use it to generate html. The method used for this is process(Map, Writer). We'll output the html content into a StringWriter. The reason is that we want to use the output to generate an HtmlUnit HtmlPage.private HtmlPage getHtmlPageFromTemplate() throws TemplateException, IOException { // Process template StringWriter out = new StringWriter(); template.process(rootMap, out); // Get HtmlPage WebClient client = new WebClient(); PageCreator pageCreator = client.getPageCreator(); WebResponseData responseData = new WebResponseData(out.toString().getBytes(), 200, "OK", new ArrayList<NameValuePair>()); WebResponse response = new WebResponse(responseData, new URL("http://localhost:8080/"), HttpMethod.GET, 10); return (HtmlPage)pageCreator.createPage(response, client.getCurrentWindow()); }There's a lot going on here, but most of it is dummy data. First, we call the process method of the Template method, and set the ouput into a StringWriter. Then, in order to create an HtmlPage, we setup a WebClient and generate a dummy response containing our html.
Declare some expectations
We want to check the following:- All page numbers are anchors tags, except the current page
- The previous and next anchors tags are present when necessary
- The number of generated anchor tags equals the number of expected anchor tags
- The current page is present, and is not an anchor tag
@Test public void testPagination_twopages_currentisone() throws IOException, TemplateException { // Make FreeMarker template setPaginationAttributes(2, 1); HtmlPage page = getHtmlPageFromTemplate(); // page links List<String> expectedContent = makeExpectedLinks(new String[]{"2"}, false, true); List<HtmlElement> pageLinks = getPaginationLinks(page); Assert.assertEquals(expectedContent.size(), pageLinks.size()); assertContainsLinks(expectedContent, pageLinks); assertCurrentPageIs(page, "1"); }First, we set our FreeMarker information and generate the HtmlPage : two pages, page one is the current page. Then, we declare some expectations. makeExpectedLinks is a method generating the expected anchor links text. We pass an array of page numbers, and whether or not the previous and next links should be present:
private List<String> makeExpectedLinks(String[] links, boolean prev, boolean next) { List<String> expectedContent = new ArrayList<String>(); expectedContent.addAll(Arrays.asList(links)); if ( prev ) { expectedContent.add("Prev"); } if ( next ) { expectedContent.add("Next"); } return expectedContent; }The getPaginationLinks extracts the anchor tags from the html content. This is where HtmlUnit becomes useful. Here, we look for the pagination div and get all anchor elements.
private List<HtmlElement> getPaginationLinks(HtmlPage page) { HtmlDivision div = page.getHtmlElementById("pagination"); return div.getElementsByTagName("a"); }We make sure that the number of links meets our expectations : Assert.assertEquals(expectedContent.size(), pageLinks.size());. Then the content of the links are checked via the assertContainsLinks method :
private void assertContainsLinks(List<String> expectedLinks, List<HtmlElement> anchors) { List<String> anchorsText = new ArrayList<String>(); for (HtmlElement htmlElement : anchors) { anchorsText.add(htmlElement.getTextContent()); } for( String expectedLink : expectedLinks ) { Assert.assertTrue("Expected page link["+expectedLink+"] was not found", anchorsText.contains(expectedLink)); } }
The current page is also checked via the assertCurrentPageIs method :
private void assertCurrentPageIs(HtmlPage page, String pageNo) { HtmlElement currentPage = getPaginationCurrent(page); Assert.assertTrue(!(currentPage instanceof HtmlLink)); Assert.assertEquals(pageNo, currentPage.getTextContent()); }
From there, it's easy to make other tests for different page numbers and current pages:
@Test public void testPagination_twopages_currentistwo() throws IOException, TemplateException { // Make FreeMarker template setPaginationAttributes(2, 2); HtmlPage page = getHtmlPageFromTemplate(); // page links List<String> expectedContent = makeExpectedLinks(new String[]{"1"}, true, false); List<HtmlElement> pageLinks = getPaginationLinks(page); Assert.assertEquals(expectedContent.size(), pageLinks.size()); assertContainsLinks(expectedContent, pageLinks); assertCurrentPageIs(page, "2"); } @Test public void testPagination_threepages_currentistwo() throws IOException, TemplateException { // Make FreeMarker template setPaginationAttributes(3, 2); HtmlPage page = getHtmlPageFromTemplate(); // page links List<String> expectedContent = makeExpectedLinks(new String[]{"1","3"}, true, true); List<HtmlElement> pageLinks = getPaginationLinks(page); Assert.assertEquals(expectedContent.size(), pageLinks.size()); assertContainsLinks(expectedContent, pageLinks); assertCurrentPageIs(page, "2"); }
Running the tests under Eclipse shows the expected green bar :
Nice article!
ReplyDelete