Functional Testing of Dynamic Websites with Grails and Geb

Holy GrailFunctional tests are like exercise: they’re hard work at the time, but in the long run you’ll be suffering more for skipping them. Yes, function tests — also referred to as system tests — can be difficult to write, especially when there is a lot of data setup required before your test begins. And yes, any time you simulate a browser your tests will take a long time to run. However, I recently discovered that, in the words of Joni Mitchell, “you don’t know what you got till it’s gone.” On my current project, we didn’t have any system test framework, and it was truly intimidating to make changes. Without those tests, we did not have any solid means of checking whether or not a change broke a feature.

So when I was asked to look into functional test frameworks using our technology stack (Groovy on Grails), I jumped at the chance. I looked around for a bit at some of the options out there, but Geb was the only viable option I saw for Groovy. Geb provides a jQuery-style interface to your web content, and has some fairly slick ways of guiding programmers to create the kinds of abstractions that help make system tests easy to write. Here’s a quick tour of what I’ve learned from merging Geb (version 0.6.3) and Grails (version 1.3.7):

The Page Class

Geb provides the Page class, which allows you to create an object which represents a web page / URL within your application. Within that subclass, you can define content in a closure:

static content = {
    username { $("input", name: "j_username") }
    password { $("input", name: "j_password") }
    signinButton(to: DashboardPage) { $("input#sign-in") }
}

When you are visiting the page in your test, you can then write highly readable code like this:

username.value("will")
password.value("abc123")
signinButton.click()

Which is much, much better than specifying the css selector in your test. Now, every test that accesses that page has access to its fields. And of course, when the inevitable refactoring of your markup comes, you’ll know right where to update your tests because they are well-organized and DRY.

Dynamic Content

AJAX is a given for any modern web page, but dynamic changes to content are the source of frequent frustration when writing functional tests. Geb attempts to counter this by providing the wait option on content declaration:

static content = {
    myDynamicContent(wait: true) { $(".dynamic-content-class") }

However, this often falls short when content may be temporarily invisible — especially when it is invisible while waiting for interior content to be loaded. The element matching the selector will be present, but of course Selenium can’t interact with an invisible element, so your test will fail. Fortunately, the wait option only looks for a value that conforms to Groovy Truth to be returned from the closure defining the content, so I created a wrapper function I call displayed(). I put this function on the base class of all my Pages, and it only returns the element if it is displayed. The end result looks like this:

static content = {
    myDynamicContent(wait: true) { displayed($(".dynamic-content-class")) }

I also ran into a different major snag related to dynamic data. Specifically, when geb-junit (the test framework integration for Geb that we used) completes all tests, it shuts down the Grails server, even if the server is still processing an AJAX request. As some of our pages frequently poll for updated data, this often create a horrible mess in our test logs (even though the last test technically passed). At present, I have what I would call a ‘passable’ solution. Essentially, I log out the user at the end of each test — so that no more AJAX requests are made — and I open a thread that waits for five seconds. This thread won’t stall the start of the next test, but it does keep the server alive for a little bit after all tests complete.

Data Bootstrapping

The last major struggle with Geb + Grails came when trying to access the database via GORM/Hibernate during tests. This is necessary for creating test fixture data before the test begins using the web browser and for verifying changes to the database after the Selenium navigation is complete. However, because of the way Hibernate works, it is essential that you manually force data changes to go to the database before you begin instrumentation via Selenium, and it is similarly essential that you refresh the data to validate your changes after instrumentation.

Ultimately, the only way I could get Grails to consistently do this correctly was by totally isolating the bootstrapping and the verification from the test code. I open a new session and a new transaction for pushing data to the database, and I open a new session for verifying changes to the database. On my base test class, I created the functions inTransaction() and inNewSession for this purpose, each of which take closures to run in the indicated context (inTransaction also creates a new Session). You can safely extract any data from these closures, such as ids or field values — anything except GORM objects, of course. Here’s an example:

def recordId
inTransaction {
    def record = new MyGormClass()
    record.doSomeSetup()
    recordId = record.id
}

to(ActionPage)
changeTheRecordButton.click()

inNewSession {
   def record = MyGormClass.get(recordId)
   assert record.changed
}

All in all, I am pleased with the Geb testing framework. It provides a good abstraction layer around writing functional tests for a website. The difficulties I ran into were circumvented fairly quickly via the strategies I mentioned above. I hope these tips come in handy if you finding yourself writing system tests for Geb and Grails. And I’d love to hear more expert advice from others who have used the framework. Iron sharpens iron, as the old saying goes.
 

Conversation
  • @Will, wonderful account of your experiences with Geb. The little notes about pitfalls you encountered were especially nice. And while I’m generally not in favour of browser-tests interacting with the database, your session handling utilities were quite creative.

    A few small observations, though:
    – I’ve left a comment on your displayed() gist describing how it could be more idiomatic and concise
    – WRT the statement wait option only looks for a non-null value to be returned from the closure; wait actually will wait until the content definition returns a value that conforms to the Groovy Truth, not just null

    – d

    • Will Pleasant-Ryan Will Pleasant-Ryan says:

      Great feedback, thanks! I’ve updated the post to reflect that wait looks for Groovy Truth, and I’ve updated the gist as well to use the more concise ? notation.

  • Luke Daley says:

    Hi,

    Great write up. You might be interested in a talk I did at SpringOne about functional testing with Grails. Slides are at; https://github.com/downloads/geb/geb-talks/productive-grails-functional-testing-springone-2012.zip

  • Comments are closed.