'Geb/Spock functional testing in Grails' post illustration

Geb/Spock functional testing in Grails

avatar

At our company’s blog we have already considered many types of the Grails testing. But there are yet one testing type, on which we have to take a look at. In this article we will check out the overview of writing functional tests using Geb/Spock technologies.

Geb and Spock is a young and fast developing technologies. Spock provides fast and convenient test writing. And Geb provides testing of the web application by the HTMLUnit and the real browsers web drivers. Let’s look how it works.
At first, let’s install geb and spock plugins.

1
2
grails install-plugin geb
grails install-plugin spock

Then you should specify following code in your BuildConfig.groovy dependencies

1
2
3
4
5
6
7
8
dependencies {
    test("org.seleniumhq.selenium:selenium-htmlunit-driver:2.0rc3") {
        exclude "xml-apis"
    }
    test("org.seleniumhq.selenium:selenium-chrome-driver:2.0rc3")
    test("org.seleniumhq.selenium:selenium-firefox-driver:2.0rc3")
    test "org.codehaus.geb:geb-spock:0.6.0"
}

At the test running stage you may experience some troubles because of conflict between this plugins and some others you have already installed to your project, one of this conflict related to "release" plugin usage. For me, adding of the following lines to the build config worked.

1
2
3
4
5
6
7
8
9
10
11
dependencies {
    //code we’ve already specified
    build('net.sourceforge.nekohtml:nekohtml:1.9.14') {
        excludes "xml-apis"
    }
}
plugins {
    build(':release:1.0.0.M3') {
        excludes "svn", 'nekohtml'
    }
}

Now, when we have everything ready, let’s get down to business. Unfortunately, as you may already notice, grails has nothing like grails create-functional-test, so you should create functional tests by yourself, specifying them at the directory test/functional or use this solution http://adhockery.blogspot.com/2010/08/auto-generate-spock-specs-for-grails.html .
The next step would be the creating page objects that you are going to use in your tests. Let’s write a simple test that is gonna test the login page generated by spring security.
Login page HTML looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<form action='${postUrl}' method='POST' id="loginForm" name="loginForm">

  <p class="form_label"><g:message code='ex.ui.login.auth.username' default="Username"/></p>
  <p><span class="login">
    <input type="text" name="j_username" id="username" size="15"/>
  </span></p>
       
  <p class="form_label"><g:message code='ex.ui.login.auth.password' default="Password"/></p>
  <p><span class="login">
    <input type="password" name="j_password" id="password" size="15"/>
  </span></p>
       
  <input type="checkbox" class="checkbox" name="${rememberMeParameter}" id="remember_me" checked="checked"/>
  <label for="remember_me"><g:message code='ex.ui.login.auth.remember' default="Remember me"/></label>
  <g:submitButton name="loginBtn" value="${message(code:'ex.ui.login.auth.loginBtn')}"/>    
</form>

We’re gonna use login page and the home page at our test. Their objects should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.sysgears.example.pages

import geb.Page

class ViewUserPage extends Page {

    static url = "admin/view"

    static at = { title == "Users" }

    static content = {
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.sysgears.example.pages

import geb.Page

class LoginPage extends Page {

    static url = "login/auth"

    static at = { title == "Login" }

    static content = {
       loginForm { $("form") }
       loginButton { $("input", value: "Login") }
    }
}

Let’s take a closer look at the properties: "at" closure specifies a condition when this is a current page. Values at the "content" property helps us to access the page elements in the tests. Notice, that access to the elements is provided in jQuery-like language, that makes accessing the elements extremely convenient. Now, let’s check out the test example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.sysgears.example;

import geb.spock.GebReportingSpec;
import spock.lang.Stepwise;
import com.sysgears.example.pages.*

@Stepwise
class LoginAuthSpec extends GebReportingSpec {

    String getBaseUrl() { "http://localhost:8080/" }

    File getReportDir() { new File("target/reports/geb") }

    def "invalid login"() {
       given: "I am at the login page"
       to LoginPage

       when: "I am entering invalid password"
       loginForm.j_username = "admin"
       loginForm.j_password = "ioguffwf"
       loginButton.click()

       then: "I am being redirected to the login page, the password I entered is wrong"
       at LoginPage
       loginForm.j_username == "admin"
       !loginForm.j_password
    }
    
    def "admin login"() {
       given : "I am at the login page"
       to LoginPage

       when: "I am entering valid username and password"
       loginForm.j_username = "admin"
       loginForm.j_password = "1234"
       loginButton.click()

       then: "I am being redirected to the admin homepage"
       at ViewUserPage
       $().text().contains("You are logged in as admin")
    }
}

Let’s take a closer look. Test methods here breaked on the sections given, when and then. 'Given' gives initial conditions for the test, 'when' specifies test behaviour and 'then' checks assertions at the end of the test. Test methods: to() method sends HTTP request to the URL, that was specified in url property of the page object; at() method checks whether specified page is current or not, for doing this condition specified at the "at" property is being used. Notice that you may access HTML elements not only via those values, which was defined in content property, but also access them directly from the test.
For more examples please see this examples from the Geb https://github.com/geb/geb-example-grails . Also Geb book should be very useful http://www.gebish.org/manual/current/

If you're looking for a developer or considering starting a new project,
we are always ready to help!