Category: Grails

The domain class

We have a domain class Project that models a project that one is working on when doing a particular work session. It records the name of the project (“Writing a blog post”), a status (“active”, “completed”), a creation time, the total time spent on this project and the user the project belongs to.

The total time is a derived field: it calculates the time spent on the work sessions that belong to this project. It should only calculate the work sessions that have status “done”. Derived fields can be defined in Grails by so-called formulas.

Testing the formula

So how do we test a formula? Initially, I tried to write integration tests (grails create-integration-test <className> that create a class with the grails.testing.mixin.Integration and grails.transaction.Rollback annotations), but these suffer from the limitation that each feature method starts a new transaction, and formula’s aren’t updated until after the transaction is committed (which is never because the transaction is always rolled back). This effectively makes it impossible for an integration test to check whether a formula does what it is supposed to do.

The solution is to write a test specification that extends HibernateSpec, which allows us to fully use Hibernate in our unit tests.

packagecom.relentlesscoding.pomotimerimportgrails.test.hibernate.HibernateSpecclassProjectTotaltimeSpecextendsHibernateSpec{def'adding a work session should increase total time'(){given:'a new project'defprojectStatus=newProjectStatus(name:'foo',description:'bar')defuser=newUser(firstName:'foo',lastName:'bar',userName:'baz',email:'q@w.com',password:'b'*10)defproject=newProject(name:'a new project',status:projectStatus,creationtime:newDate(),totaltime:0,user:user)expect:'total time of the new project is 0 seconds'project.totaltime==0when:'adding a completed work session to the new project'defduration=newDuration(seconds:987)defdone=newWorkSessionStatus(name:'done',description:'done')newWorkSession(starttime:newDate(),duration:duration,project:project,status:done.save(failOnError:true,flush:true)// NOTE: a refresh is required to update the domain objectproject.refresh()then:'total time is equal to duration of completed work session'project.totaltime==987}}