Friday, August 3, 2012

Grails: testing rollback of transactional service methods

Grails is so easy to dive into, but one quickly starts to encounter apparently nebulous corner cases. One such case is the testing of transactional rollback or a service method. Take the following example:

class SimpleService {
  saveMyDog(Dog dog){
    dog.save()
    throw new RuntimeException("don't save my doggie")
  }
}

Service methods are bound to a transaction by default, so because of the RuntimeException being thrown, in the above method the dog should not be saved/persisted.

Not transactional behaviour is database dependent. Be sure you're using InnoDB!

Now lets test the rollback behaviour we expect from our method

class SimpleServiceTests extends GroovyTestCase {

  def simpleService

  void testSaveMyDog(){
    def existingDogCount = Dog.count()
    simpleService.saveMyDog(new Dog())
    def dogCountStayedTheSame = existingDogCount == Dog.count() ? true : false
    assert dogCountStayedTheSame
  }
}

The above test fails. What ?? Our rollback doesn't work ? Well, not quite. Our rollback is not working in our test. The reason for that is that as well as Services being executed in a transaction so too are integration tests. So because there is already a transaction in play, there is no transaction bound to the method of the service. Thus, the rollback never happens. One more thing to note, is that there is a very good reason tests are run in a transaction: it reduces the need for lots of tear down code to delete any data created within a test. Thus, any test we make non-transactional we need to add appropriate tearDown code. With that in mind, I recommend creating an individual test class, to keep a test like this separate from any other tests. Finally to the code - the working, test-passing code!

class SimpleServiceTests extends GroovyTestCase {
  static transactional = false
  def simpleService
  
  void setUp(){
    // any setup here
  }

  void testSaveMyDog(){
    def existingDogCount = Dog.count()
    simpleService.saveMyDog(new Dog())
    def dogCountStayedTheSame = existingDogCount == Dog.count() ? true : false
    assert dogCountStayedTheSame
  }
 
  void tearDown(){
    // tidy up here
  }
}