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
  }
}

Comments

  1. Very explained the importance of integration testing within the context.

    Nice Job! Keep it up.

    Bests,
    Sally Sen

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. I believe this statement is not entirely true: "Service methods are bound to a transaction by default" The situation is that service methods are bound to a transaction ONLY in the following 4 cases: 1) the method is annotated with @Transactional (or similar), or 2) the class is annotated with @Transactional or similar, or 3) "static transactional = true" is defined AND there is no @Transactional on any method (if so the static is silently ignored switching off default Transactional for the whole class), or 4) it is called by something which already has a transactional scope

    ReplyDelete
    Replies
    1. Simon, that is true, but only since Grails 3.1. Before that, services were transactional by default, so it was equivalent to having "static transactional = true" in all service methods.

      Delete

Post a Comment

Popular posts from this blog

AngularJs: User friendly date display with AngularJs and MomentJs

Getting started with Grails functional tests using Geb + Spock

Nerd Tree: A File Explorer with Mac Vim