Grails: grails-cucumber and compiled steps

I am finally finishing the precompiled steps support in the cucumber grails plugin.

Currently if we run grails test-app functional:cucumber it will fire up the whole application and then run cucumber. Cucumber will load, parse and execute all our groovy step source files. If there is an import missing in one of the source files (we will look at an example in a minute) it will throw an error at us. At runtime.

Compiling the cucumber glue code has the advantage that we can catch this already at compile time before grails runs the application. Depending on the size of your app this can save you a number of time consuming round trips.

Let’s take the grails-cucumber Book example and remove the BookController import from BookSteps.groovy.

//disabled the import below...
//import books.BookController
import data.Data

import static cucumber.api.groovy.EN.*

...
When (~'^I add "([^"]*)"$') { String bookTitle ->
    bookController = new BookController ()
    bookController.params << Data.findByTitle (bookTitle)
    bookController.add ()
}
...

The When step create a new BookController. Now if we run it with the current version of grails-cucumber (0.8.0) we will get the following output:

Prompt:Books hauner$ grails test-app functional:cucumber
| Environment set to test.....
| Packaging Grails application..
| Packaging Grails application.....
| Server running. Browse to http://localhost:8080/Books
| Compiling 2 source files.
| Error Compilation error compiling [cucumber] tests: startup failed:
/Users/hauner/Development/Grails/grails-cucumber.git/test/projects/Books/test/functional/steps/BookSteps.groovy: 12: unable to resolve class BookController 
 @ line 12, column 22.
       bookController = new BookController ()
                        ^

/Users/hauner/Development/Grails/grails-cucumber.git/test/projects/Books/test/functional/steps/BookSteps.groovy: 33: unable to resolve class BookController 
 @ line 33, column 22.
       bookController = new BookController ()
                        ^

2 errors
 (Use --stacktrace to see the full trace)

We can see that grails is packaging the application and starts the server, then cucumber starts loading the step code and fails as expected.

Now using the new precompiled steps feature it will look like this:

Prompt:Books_compile hauner$ grails test-app functional:cucumber
| Environment set to test.....
| Compiling 1 source files.
| Error Compilation error compiling cucumber glue code:
            startup failed:
/Users/hauner/Development/Grails/grails-cucumber.git/test/projects/Books_compile/test/cucumber/steps/BookSteps.groovy: 14: unable to resolve class BookController 
 @ line 14, column 22.
       bookController = new BookController ()
                        ^

/Users/hauner/Development/Grails/grails-cucumber.git/test/projects/Books_compile/test/cucumber/steps/BookSteps.groovy: 35: unable to resolve class BookController 
 @ line 35, column 22.
       bookController = new BookController ()
                        ^

2 errors
 (Use --stacktrace to see the full trace)

No packaging and no server startup before it fails. Cool ;-)

To use precompiled steps we only need to modify the plugin configuration a little bit. To make this more interesting I have also moved the step code to another directory to separate the feature description from the implementation.

The default layout I use in the Book examples has features and step code in the functional folder.

test
  \-- functional
        \-- data
              Data.groovy 
        \-- hooks
              env.groovy
        \-- steps
              BookSteps.groovy
        ListBooks.feature
        NewBook.feature

I changed the layout for the new Book_compile example to:

test
  \-- cucumber
        \-- data
              Data.groovy 
        \-- hooks
              env.groovy
        \-- steps
              BookSteps.groovy
  \-- functional
        ListBooks.feature
        NewBook.feature

I choose cucumber for the source files but it can by anything you like. Apart from the source directories we have to tell cucumber were it will find the (compiled) steps.

This will look like this (CucumberConfig.groovy):

cucumber {
     // steps, hooks etc that will be compiled
    sources = ["test/cucumber"]

    // .. and where cucumber will find the compiled steps & hooks
    glue = ["classpath:steps", "classpath:hooks"]
}

With the (new) sources directive we set the source directories and with the (existing) glue directive we tell cucumber where it will find the step code in the classpath by prefix the entries with classpath:.

In my original example the cucumber source files do not have a package name. This is bad for compiling the steps because we would have to tell cucumber to start searching for them at the default package. This is not a good idea because cucumber would check all .class files in the whole classpath to find the glue code.

It is better to properly package the step code so we can make sure cucumber will only look at classes it cares about.

In the example I have used multiple top level packages to show that it is possible. Usually you should place it under the same parent package so that there is only a single classpath: required for the configuration.

Note that we did not add the data path to glue. It is just a helper class that does not contain cucumber code, so we do not need to tell cucumber about it. If we add it, cucumber will ignore it.

test/functional

if you want to keep the source below test/functional that is ok too. You do not need to set it as sources in CucumberConfig.groovy but you still have to adjust the glue settings.

that’s it…

I hope you will like this new feature of grails-cucumber. I still have some cleanup todo before I will release it, but it is on its way.. :-)

Advertisements

Grails: JSONBuilder/render vs JsonBuilder

I had some fun creating a custom json in Grails. There is grails.web.JSONBuilder and groovy.json.JsonBuilder. The former seems to be deprecated (according to the grails documentation). But as it uses the same api to create json as the render method I looked at it anyway.

Both builder do create json but they are, let’s say unintuitive, when it it comes to arrays with nested objects. If you ask google a lot of people seem to fight with this as well. Often the answers are not enlightening either.

It looks like there is no other way than to read the docs … ;-)

How hard can it be?

This blog will show a number of simple examples that will hopefully help you understand JSONBuilder/render and JsonBuilder.

To concentrate on the json, the examples will be stripped of some boilerplate code and uses a few simple domain classes as test data:

JsonBuilder (groovy)

The examples are based on the following code snippets:

JsonBuilder json = new JsonBuilder ()
def map = json {
    ...
}

String result = json.toString ()

// json
{
  ....
}

 

JSONBuilder/render (grails)

JSONBuilder jSON = new JSONBuilder ()
JSON json = jSON.build {
    ....
}
String result = json.toString ()

// ... uses the same api as JSONBuilder without new,
// toString () and build
render (contentType: "text/json") {
    ....
}

 

  • to reduce the noise the examples will skip the new and the toString () lines
  • for easier reading the resulting json is formatted manually

 

Some of the examples use test data based on the following domain classes.

Example Domain Classes

class Artist {
    String name
}

class Song {
    String title
}

class Album {
    String title
    Artist artist
    static hasMany = [songs:Song]
    ....
}

 

Test Data Setup

Artist prettymaids = new Artist (name: "Pretty Maids")
Album motherland = new Album (title: "Motherland", artist: prettymaids)
motherland.addToSongs (new Song (title: "Mother of all Lies"))
motherland.addToSongs (new Song (title: "To fool a Nation"))
motherland.addToSongs (new Song (title: "Confession"))
motherland.addToSongs (new Song (title: "The Iceman"))

 

Now let’s look at some examples..

 

JsonBuilder (groovy)

 

an empty object

 

def map = json {
}

// json:
{}

 

simple properties

 

def map = json {
    title "Motherland"
    artist "Pretty Maids"
}

// json
{
    "title": "Motherland",
    "artist": "Pretty Maids"
}

 

simple nested object

 

def map = json {
    title motherland.title
    artist {
        name prettymaids.name
    }
}

// json
{
    "title": "Motherland",
    "artist": {
        "name": "Pretty Maids"
    }
}

 

The same result is also achieved by using named arguments.

def map = json {
    title motherland.title
    artist (name: prettymaids.name)
}

 

.. more simple nesting

 

We can combine named arguments with the property methods.

def map = json {
    title motherland.title
    artist (name: prettymaids.name, country: {
        name "Danmark"
    })
}

// json
{
    "title": "Motherland",
    "artist": {
        "name": "Pretty Maids",
        "country": {
            "name": "Danmark"
        }
    }
}

 

simple list

 

def map = json {
    list 1,2,3,4
}

// or...
def map = json {
    list ([1,2,3,4])
}

// json
{
    "list": [1,  2,  3,  4]
}

So far so good.

 

list with objects

 

Now it gets a little bit strange.. A property accepts a list as value as we have seen in the previous example. If we have objects in our list we can can create an argument list for the songs property by using the lists collect method and converting each object to a map.

def map = json {
    title motherland.title
    songs motherland.songs.collect { Song s ->
        [title: s.title]
    }
}

// json
{
    "title": "Motherland",
    "songs": [{
        "title": "To fool a Nation"
    }, {
        "title": "The Iceman"
    }, {
        "title": "Confession"
    }, {
        "title": "Mother of all Lies"
    }]
}

But why a map, I want to use the builder api!

We will have to write it like this to get the same output using the builder api:

def map = json {
    title motherland.title
    songs motherland.songs.collect { Song s ->
        json {
            title s.title
        }
    }
}

To create json for the nested objects (the songs) using the api we call the builder (json) again inside the collect closure. It will create the map for each song we have hand crafted in the above version.

We could also write:

def map = json {
    title motherland.title
    songs motherland.songs.collect { Song s ->
        songs {
            title s.title
        }
    }
}

using songs instead of json to call the builder. But I think that just adds to the confusion. In this case the inner songs seems to be ignored but if we call it something else, like songs2 we will get a song list and a simple song2 property with the last song as value.

I prefer the first version which is unintuitive enough. ;-)

As far as I understand the JsonBuilder there is no easier way to handle nested objects. Unfortunately it is not very user friendly.

I would like to write it like this:

def map = json {
    title motherland.title
    songs motherland.songs, {
        title s.title
    }
}

The builder would loop through motherland.songs and call the given closure to build each list items json.

 

JSONBuilder/render (grails)

 

Now a couple of similiar examples using grails.

 

an empty object

 

JSON json = jSON.build {
}

// json:
null

Using an empty json block in the render method will fail with a NullPointerException (in Grails).

 

simple properties

 

JSON json = jSON.build {
    title = motherland.title
    artist = prettymaids.name
}

// json
{
    "title": "Motherland",
    "artist": "Pretty Maids"
}

 

simple nested object

 

JSON json = jSON.build {
    title = motherland.title
    artist = {
        title = prettymaids.name
    }
}

// json
{
    "title": "Motherland",
    "artist": {
        "name": "Pretty Maids"
    }
}

 

list

 

JSON json = jSON.build {
    title = motherland.title
    songs = array {
        unused {
            title = "Mother of all Lies"
        }
        unused {
            title = "To fool a Nation"
        }
        unused {
            title = "Confession"
        }
        // or like this:
        _ {
            title = "The Iceman"
        }
    }
}

// json
{
    "title": "Motherland",
    "songs": [{
        "title": "To fool a Nation"
    }, {
        "title": "The Iceman"
    }, {
        "title": "Confession"
    }, {
        "title": "Mother of all Lies"
    }]
}

There are two things to note. First we can create lists with the explicit array method. Second the unused. We have to call a method on the builder to create the objects of the list, but this time the method name is not turned into a property name.

We can use anything here, it is just necessary so the closure can be called. Shortest and with fewest noise is probably to use _ {..} to create a list object.

Of course, we can use a loop to to create the song list:

JSON json = jSON.build {
    title = motherland.title
    songs = array {
        for (s in motherland.songs) {
            _ {
                title = s.title
            }
        }
    }
}

 

top level list

 

Now as the last example there is the weird create a list as top level element construct which goes like this:

JSON json = jSON.build {
    for (i in motherland.songs) {
        element ([title: i.title])  // parameters must be a map
    }
}

// or like this
JSON json = jSON.build {
    for (i in motherland.songs) {
        element {
            title = i.title
        }
    }
}

// json
[
    {"title":"The Iceman"} ,
    {"title":"Mother of all Lies"},
    {"title":"Confession"},
    {"title":"To fool a Nation"},
]

element is a special keyword here. Used at the top level it will create a list as the top level element.

This does not work in the render method by the way, it will create:

{
    "element": {
        "title":"To fool a Nation"
    }
}

 

That’s it.

 

You can get all (most) of this from the documentation. You may have to read it more than once though.. :-)

If you look after JsonBuilder you will find the api documention and at first sight an example that leaves some open questions. But it is all there in the api docs. Make sure you look at the additional examples of each method.

JSONBuilder and the render method are described in the Grails documentation: creating json, render and builder api.

JSONBuilder/renders explicit array method is a lot easier to understand than the collect expression we had to use with JsonBuilder, but the unused property on the list object in both builders is confusing from a user perspective.

Personally I prefer JsonBuilders notation. I only dislike the way we have to handle arrays. It would be a lot easier to understand if we could just write:

def map = json {
    songs motherland.songs, {
        title s.title
    }
}

 

Running git clone git@github.com:hauner/groovy-core.git groovy-core.git now …. :-)

Update: May 2014

My patch for the improved array handling was merged and is part of groovy 2.3.0.