Thursday, April 2, 2009

Running Grails unit tests in Intellij IDEA

In my previous post, I suggested that I'd have some useful ideas to share with you. In this post, I hope to deliver on that promise by explaining the frustrations I encountered while unit testing a Grails project with IntelliJ IDEA, and the workaround I came up with to ease my pain.

First, some background. I like Grails - a lot. It dispenses with a lot of the gunk that makes developing web apps in Java so tedious: XML configuration files, repetitive boilerplate code, tag library descriptors. Grails utilizes proven technologies like Spring, Hibernate, and SiteMesh for its core functionality rather than re-inventing the wheel. And it stole, um, borrowed, lots of great ideas from Ruby on Rails, including the use of a dynamic language, i.e. Groovy, to underpin the whole framework.

Grails also has the best testing support of any framework I've used: automatic generation of test classes, an integrated test environment with an in-memory database, and with version 1.1, comprehensive mocking support for unit testing. The Grails team seems determined to eliminate every conceivable hindrance to testing, and that's an admirable goal.

However, I don't love absolutely everything about Grails testing. In particular, Grails' mechanism for running unit tests is painfully slow. The Grails test-app command runs both unit and integration tests by default, but with the -unit parameter, it runs just the unit tests and doesn't bootstrap an integration test environment. This speeds up the test cycle considerably, but it's still pretty slow; on my machine it takes Grails 10 seconds to run the unit tests - even for a project with no tests!

Ten seconds may not seem like a lot of time, but if you're trying to practice a "test a little, code a little" style of TDD, it's like sand in the gears. When I'm working with unfamiliar code or attempting a tricky refactoring, I like to run the unit tests after every change that could conceivably work. That way, if the tests fail, I can undo my change without having wasted much time or effort.

However, when it takes 10 seconds to run a unit test, there's a temptation to skip the tests, stay in the flow, and keep coding. When I finally run the tests, there's always a failure of some kind, and fixing it usually necessitates a substantial reworking of the code. The longer it's been since I ran the tests, the more rework is required.

After getting bit by this a few times, I started looking for a better way to run Grails unit tests. My first instinct was to run the tests from within my IDE: there's nothing more convenient than firing off your tests with a keyboard shortcut. Unfortunately, I discovered that IntelliJ's "Grails Tests" run configuration just delegates to Grails' test-app script. While running the unit tests from within IntelliJ was a improvement, it wasn't any faster than using the command line.


Since Grails tests inherit from junit.framework.TestCase, my next tactic was to run the tests with IntelliJ's JUnit test runner:


This was a considerable improvement. The tests ran significantly faster - two or three seconds instead of ten - and the results appeared in IntelliJ's Run window, with a summary of failed tests and clickable links in the stack traces. Very nice!


There was one problem, though. As you can see from the above screenshot of the JUnit run configuration, IntelliJ has three options for selecting which tests to run: a single test method, a single test class, or all tests in a package. None of these options works very well for unit testing Grails projects. A single class was too limiting, since I didn't want to maintain a test suite with a reference to each unit test class. Conversely, the "all tests in a package" option was too permissive: it would run both the unit and integration tests, and without a Grails test environment, most of the integration tests would fail.

I wanted an option that would run just the tests in the test/unit directory, but IntelliJ's built-in test runner couldn't be configured to do that. After much fruitless searching, I finally found the solution on the Groovy website. Groovy has a utility class named AllTestSuite that finds all the tests that match a filename pattern in a given directory and aggregates them in a test suite. Normally, AllTestSuite gets the base directory and the filename pattern from the system properties, but it's easy enough to extend the class and specify the directory and pattern with static variables, like so:

import junit.framework.Test

public class AllUnitTestSuite extends AllTestSuite {

private static final String BASEDIR = "./test/unit"
private static final String PATTERN = "**/*Tests.groovy"

public static Test suite() {
return suite(BASEDIR, PATTERN)
}

}

I put the above code in a file named AllUnitTestSuite.groovy in the project's test/unit directory, ran it with IntelliJ's JUnit test runner, and it worked perfectly. Fast, convenient, and exactly what I want: problem solved!



A nice bonus here is that under the covers, AllTestSuite uses a Gant script to collect the tests, so if you're familiar with Ant FileSets, you can tweak the matching pattern to customize your test suites.

3 comments:

  1. Helpful hint. Thanks !
    You might want to try domainMock-ing to simulate db crud types of things in unit test mode.. I finally figured out how to get that working with intellij. see > http://buildchimp.com/wordpress/?p=25

    regards -
    chris bedford
    lead lackey
    build lackey labs...

    ReplyDelete
  2. Thanks, Chris. Actually mockDomain and mockForConstraintsTests are the main reason I looked into using IntelliJ's test runner; I didn't understand why it was taking so long to run tests that didn't need an integration test environment.

    I haven't run into any problems using mockDomain with IntelliJ, but that might be because I don't use Maven. ;)

    ReplyDelete
  3. Thanks,

    Your posting helped to save me some time. I ran into the same issue. Also, running the tests like this will allow generating code coverage data.

    Ben

    ReplyDelete