Grails: how to test a rolling back service’s method

Posted by – October 15, 2011

A few day ago I was writing a financial service involving several related domain classes. Under some circumstances I wanted the entire transaction to rollback.

According to the documentation, a Grails transaction is rolled back whenever a RuntimeException is thrown during its execution (this is the default behaviour in Grails, but you can configure it to rollback a transaction for whatever exception you want).

When I finished I wrote some integration tests to make sure that everything worked as expected.

It didn’t.

Basically, the exceptions were thrown but the transactions were never rolled back. It tooks me a lot of hours to understand why.

Let’s show it with an example. Just say we have the ubiquitous Book and Author domain classes:

class Author {
    String name
    static hasMany = [books: Book]
}
class Book {
    String title
    static belongsTo = [author: Author]
}

and this is our example service:


class LibrarianService {

    static transactional = true

    def createBooks() {
        def authorETAH = new Author(name: 'Ernst Theodor Amadeus Hoffmann')
        def authorHPL = new Author(name: 'Howard Phillips Lovecraft')

        authorETAH.addToBooks(new Book(title: 'Gli elisir del diavolo'))
        authorETAH.addToBooks(new Book(title: 'L uomo della sabbia'))

        authorHPL.addToBooks(new Book(title: 'Il caso di Charles Dexter Ward'))

        authorETAH.save(failOnError:true)
        authorHPL.save(failOnError:true)

        // I've changed my mind! let's rollback everything
        throw new RuntimeException('changed my mind')
    }
}

Now, you would expect the following integration test to pass:

class TransactionalTests extends GroovyTestCase {

    static transactional = true

    def librarianService

    void testRollback() {
        shouldFail(RuntimeException) {
            librarianService.createBooks()
        }

        assertEquals 0, Book.count()
        assertEquals 0, Author.count()
    }
}

on the contrary, the assertEquals statements will both fail.

Why?

As we said, every service method call is bounded in its own transaction, unless it is already inside a transaction. That is the case when the integration test class declares to be transactional with the default line:

static transactional = true

indeed, if you change it with:

static transactional = false

the test will pass.

What does this change implies for your test class? It implies that test methods are no more wrapped in transactions and automatically rolled back, so you’ll have to take care of cleaning the house after every test!

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

What is 2 + 11 ?
Please leave these two fields as-is:
IMPORTANT! To be able to proceed, you need to solve the following simple math (so we know that you are a human) :-)