Testing your Grails scripts

One of the things that has longed bugged me with Grails is how difficult it is to test scripts. In the past I have pretty much resorted to manually testing the corresponding commands and hoping that nothing bad happens. As we all know, this isn’t a very reliable approach. So, I finally caved in and knocked up a system for testing Grails scripts. I even integrated it into the normal Grails test framework!

This feature is only available in the latest Grails snapshots, from build 441 onwards, so don’t bother trying this with 1.2-M3 or earlier. It will be available in 1.2-RC1 assuming that the feature isn’t pulled (an unlikely event). That’s all you need. No extra plugins or libraries are necessary. So without further ado, let’s create a new Grails script with a test.

A test-driven script

“Hello world” examples are all the rage, so who am I to buck the trend? In this instance, we’ll create a script that echoes “Hello world” to the console and also creates a file. Since I have my TDD hat on, the first step is to create a test case for the new script.

All script tests go into the test/cli directory, so create the file test/cli/HelloWorldTests.groovy and open it in an editor. The first version looks like this:

import grails.test.AbstractCliTestCase
import org.apache.commons.codec.digest.DigestUtils

/**
 * Test case for the "hello-world" Grails command.
 */
class HelloWorldTests extends AbstractCliTestCase {
    void testDefault() {
        execute([ "hello-world" ])

        assertEquals 0, waitForProcess()
        verifyHeader()

        // Make sure that the script was found.
        assertFalse "HelloWorld script not found.", output.contains("Script not found:")
    }
}

Short and sweet. First off, we extend the AbstractCliTestCase class so that we can run our target script easily and reliably, while capturing its console output. Don’t leave home without it! Or at least don’t try to write a script test without it.

The test itself, i.e. the body of the testDefault() method, demonstrates two of the most important methods provided by AbstractCliTestCase. The execute() method takes a list of command arguments, the first of which should be the name of the command you’re testing. Since our hello-world command doesn’t accept any arguments yet, we only pass the string “hello-world” to execute(). Once invoked, this method initiates a new Grails process to execute the given command.

Once the separate Grails process has started, the execute() method returns. That means your test code is back in control before the Grails command has actually done anything. What we really need to do now is wait for the Grails command to finish. Enter waitForProcess(). This behaves very simply: it will wait for the command started by execute() to finish and then return the exit code of the process. Successful completion of a command typically results in an exit code of 0, so that’s what we test for in our assert.

Now that the command has finished, we can check what the output of the command was. At this stage, we just want to check that the command was started successfully. The verifyHeader() method checks that the usual stuff printed by Grails when it starts appears in the command output. We then check that Grails didn’t print the message indicating that the command couldn’t be found. See how we check an output property? It’s provided by the abstract test case and contains the full output of the Grails command.

Running the CLI tests

So, with the test in place, let’s try running it:

grails test-app --other
This will run all tests in the “other” phase, which is the phase the CLI tests are part of. The result of this command should contain the following:

-------------------------------------------------------
Running 1 cli test...
Running test HelloWorldTests...
                    warning...FAILED
Tests Completed in 200ms ...
-------------------------------------------------------
Tests passed: 0
Tests failed: 1
-------------------------------------------------------

A quick look at the corresponding test report will show that our assertion failed: the HelloWorld script couldn’t be found. That’s easily fixed. Create a new script, scripts/HelloWorld.groovy, and put the following in it:

includeTargets << grailsScript("_GrailsSettings")

target(default: "A simple hello world script.") {
}

When you run the tests again, you will find that our test case passes now. Whoohoo! Of course, it’s still not doing anything.

Another cycle of TDD

Let’s now augment our test case by checking that the command echoes the string “Hello world” to the console and that it also creates a “dummy.txt” file in the target directory:

...
    void testDefault() {
        def targetFile = new File("target", "dummy.txt")
        targetFile.delete()

        execute([ "hello-world" ])

        assertEquals 0, waitForProcess()
        verifyHeader()

        assertFalse "HelloWorld script not found.", output.contains("Script not found:")
        assertTrue "Hello world message is missing.", output.contains("[echo] Hello world!")
        assertTrue "dummy.txt doesn't exist", targetFile.exists()
    }
...

Run the tests and you’ll find the test case is failing again, as expected. Adding the following to your new script should do the trick:

target(default: "A simple hello world script.") {
    ant.echo("Hello world!")
    new File(grailsSettings.baseDir, "target/dummy.txt").createNewFile()
}

That’s it. The command is doing what was originally required of it and now you have a test case to ensure that it doesn’t break.

User input

You should be able to extrapolate what we’ve done here to lots of scripts, but there is still one class of script that we need to handle: those that accept user input. For example, many of the “create” scripts prompt the user for a name. Others check whether the user wants to overwrite existing files. Fortunately, AbstractCliTestCase can handle this.

Let’s say we’re running the generate-views command for a given domain class and that the views already exist. The code to test this should start:

    void testGenerateViewsWithExistingViews() {
        execute([ "generate-views", "org.example.MyDomain" ])
        enterInput("n")
        enterInput("a")

        assertEquals 0, waitForProcess()
        verifyHeader()

        // Check that first view was not overwritten, but the others were.
        ...
    }

As you can see, you can have as many calls to enterInput() as you want. But remember that you should only have as many as there are requests for input from the command. Typically, the generate-views command will request input four times, once for each view. But by entering “a” the second time, the remaining views are automatically overwritten without the user being asked.

If you’re interested in a more full-featured example, my DTO Plugin has some CLI tests for its generate-dto command. Happy testing!

6 thoughts on “Testing your Grails scripts

  1. Rob Fletcher

    Nice work. I was trying to write a test a couple of weeks back to ensure Selenium artefacts didn’t end up in a project’s war file and gave up. Now I’ll try revisiting that using cli tests.

  2. Nicolas Lupien

    Hi, thank you for this post its very useful.
    Is there a way to mock domain classes with AbstractCliTestCase ? It’s because i use scripts to input data.

  3. Pingback: Linkdump for May 22nd | found drama

Leave a Reply

Your email address will not be published. Required fields are marked *