Grails Build-test-data Plugin Released!

| Comments

Creating maintainable test data is hard.

Often an entire object graph needs to be created to support the instantiation of a single domain object. This leads to either the cutting and pasting of that creation code, or relying on a canned set of objects that we’ve grown over time and maintained as the domain objects change. After a while, adding just one more Widget to that set of canned data ends up breaking tests just about every time.

There has to be a better solution, right?

Yep! Due to the “Power and the Glory of Grails”™, we have a lot of metadata at our fingertips about those domain objects. We know what validation constraints we’ve placed on our objects, and which objects depend on other objects to live.

Using this additional information, Joe Hoover and I have created a grails plugin called “build-test-data”. With it, you can create test data and omit the fields that you don’t care about for your test. The plugin takes care of populating the values required by constraints and persists your domain objects to the database.

This plugin is focused on creating test data for integration testing. If you’re doing unit testing, there are a number of mocking solutions out there (including a few ways to do it built right into groovy). Unit testing only goes so far though, and if you want to ensure that your code works when it’s hooked up to a real database, with real transactions, saving, triggers, etc, you’re only going to get that from an integration test.

Installation of the plugin is easy. Just run this grails task in the root of your project:

grails install-plugin build-test-data

When the plugin is installed, most domain object should be able to be instantiated and persisted out of the box.

// automatically creates/saves a Book with all required fields populated
def book = Book.build()  

Once installed, all you have to do is call the new build() method on your domain class and you’ll be given a valid instance that has been persisted to the database.

It will populate the required fields as defined by the constraints and it has the ability to deal with almost all of them.

A couple of them, such as custom validations and regular expressions, are very difficult to work with so if a sample value that we try doesn’t get past validation, we’ll throw an error.

Build Test Data Example

Here’s a quick example. Say you have the following domain objects:

// grails-app/domain/Author.groovy
class Author {
    String firstName
    String lastName
    static hasMany = [books: Book]    
}

// grails-app/domain/Book.groovy
class Book {
    String title
    Date published
    BigDecimal price
    static belongsTo = [author: Author]    
}

Remember that grails properties are required unless nullable is explicitly set to false, so all of those properties are required.

If you’re testing a BookPurchasingService and the method you’re calling needs a Book, normally you’d need to also create an Author to go with it. Here’s how you might normally create your test data without the build-test-data plugin:

void testPurchaseBookService() {
    def author = new Author(firstName: "first", lastName: "last")
    author.save()
    Book tenDollarBook = new Book(author: author, title: "title", published: new Date(), price: 10.00 as BigDecimal)
    tenDollarBook.save()
        
    def result = service.purchaseBook(tenDollarBook)
    
    //... assertions about expected result of service call
}

You need to create an Author just to create the Book, even though your service only cares about the Book. If the Author class ever adds an additional required field, you not only need to update all of your Author test classes, but all of your Book test classes as well.

Additionally, there’s all kinds of “noise” in your test class that makes it more difficult to understand exactly what it is you’re trying to test.

If you’ve got the build-test-data plugin installed, you don’t need to worry about any of that. Creating sample data is easy and you can specify only the parameters that you actually need specific values in to exercise your test. The plugin will fill in the rest and persist it to the database automatically.

void testPurchaseBookService() {
    def result = service.purchaseBook( Book.build(price: 10.00 as BigDecimal) )
    
    //... assertions about expected result of service call
}

Behind the scenes, build-test-data populates the Book.title and Book.purchased fields, it also creates a new Author with a firstName and lastName and saves all of that to the database.

This test is much cleaner as you only see the things that actually matter to the test and none of the administrative flotsam and jetsam needed to set things up.

In the repository on the bitbucket website, there is a test “bookstore” grails application that has a fairly complex domain model that exercises the build-test-data build method and it’s ability to serialize an object graph.

Check out the full set of documentation on the bitbucket build-test-data wiki and let Joe and I know what you think of it.

Comments