Automated tests for GSP views in Grails

Friday 26th February, 2010

Test-driven development (TDD) is handy if used sensibly, and we’re feeling the need to make our automated tests a little broader. The Grails site has great documentation on setting up tests for Controllers and Services, but I couldn’t find a decent explanation of how to set up tidy automated tests for GSPs… so without further ado this is what I did.

I want tests to be:

  • Easy to write (and hence read)
  • Low maintenance, i.e. I don’t want to have to update tests whenever I make changes that aren’t important to the test
  • Good at picking up unexpected behaviour rather than changes to HTML structure

Running your GSP from TestApp

Grails ships with a handy class called GroovyPagesTestCase which allows your test to get the output of a given GSP file given a defined input, like this:

def file = new File("grails-app/views/myview.gsp")
def model = [someVariable:12345]
def htmlString = applyTemplate( file.text, model )

We’re passing in the text from the GSP file as a template, along with a model comprised of whatever variables and mock objects the view should need.

Now I’ve got a string containing a bunch of HTML, okay, that’s the right direction but my lazy gene is not satisfied yet.

Note 1: If your template calls other templates then it makes life easier to use absolute URLs, e.g. templateUrl"/>

Note 2: This method assumed you’ll specify an explicit model for each sub-view, e.g. myObj:myObj]}"/>

From a sticky HTML mess to something useful

The easiest way to get from unparsed HTML to a useful searchable structure seems to be Groovy’s XMLSlurper. It parses XML rather than HTML by default, but you can instantiate it with a more HTML-friendly parser like TagSoup like so:

def slurper = new XmlSlurper( new org.ccil.cowan.tagsoup.Parser() )
def parsedHtml = slurper.parseText(text)

Easy.

Pulling it all together

import grails.test.GroovyPagesTestCase
import org.ccil.cowan.tagsoup.Parser

class GspXyzTests extends GroovyPagesTestCase
{
	boolean transactional = false
	def slurper = new XmlSlurper( new Parser() )

	// This test looks for a specific thing in the resulting parsed HTML
	void testSomeImportantOutput() {

		//Open file containing GSP under test
		def file = new File("grails-app/views/myfolder/template.gsp")
		assertNotNull(file)

		//Obtain result of GSP page
		def text = applyTemplate(file.text, [
			pagenumber:123,
			pagesize:10,
			someMockObject:[
				foo:"bar",
				nestedMockObject:[
					[id:12345],
					[id:67890]
				]
			]
		])

		def html = slurper.parseText( text )

		// test some aspect of the parsed structure, the trick would be to make the test resilient to a degree of cosmetic change
		assertEquals 1, html.head.meta.list().findAll{ it.@name?.text().toLowerCase() == "robots" }.size()

	}

}
Advertisements

Grails Exchange in December

Monday 23rd November, 2009

It’s great to see fellow Pixstanaut Tomás Lin talking at the forthcoming Grails Exchange conference in December. He’ll be talking about building rich GUI apps with Flex and Grails. There are still a few tickets left if you can make it.


6 Weeks in, Grails hasn’t disappointed

Wednesday 25th February, 2009

Today I came across a slightly annoying bug in the Groovy compiler that means that static variables declared in interfaces don’t always make the conversion from Groovy to Java intact. Everything after the static variable name is lost.

While bugs like this are always frustrating, I actually consider it a pretty good sign that this is the only real obstacle that this framework has presented so far.

We’re due to release some new code at the beginning of March, including a new version of our visual similarity engine, so expect posts about related topics soon.


New web platform

Tuesday 13th January, 2009

At Pixsta this week we chose our development platform for the next stage of the team’s development. We evaluated a lot of tools under a variety of criteria, and debated long and hard.

In the end we chose Grails, the Rails-like web framework for the Groovy language. Reasons include:

  • It runs on a familiar Java stack (Hibernate, Spring, etc.)
  • Script is compiled to Java bytecode
  • It uses convention over configuration
  • We have a few projects under our belts with this framework already

Goodbye PHP, we will not meet again.