Unit Test Grails GORM Formulas
Let’s take a look at how we can test GORM formulas in Grails.
The code for this post is part of my PomoTimer
project and can be
found on GitHub.
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.
package com.relentlesscoding.pomotimer
class Project {
String name
ProjectStatus status
Date creationtime
Integer totaltime
User user
static constraints = {
name blank: false, nullable: false, maxSize: 100, unique: true
status blank: true, nullable: false, display: true
creationtime blank: false, nullable: false
totaltime blank: false, nullable: false, min: 0
user blank: false, nullable: false, display: true
}
static mapping = {
totaltime formula: '(select ifnull(sum(select d.seconds from Duration d where ws.duration_id = d.id), 0) from Work_Session ws where ws.project_id = id and ws.status_id in (select st.id from Work_Session_Status st where st.name = \'done\'))'
}
String toString() { return name }
}
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.
package com.relentlesscoding.pomotimer
import grails.test.hibernate.HibernateSpec
class ProjectTotaltimeSpec extends HibernateSpec {
def 'adding a work session should increase total time'() {
given: 'a new project'
def projectStatus = new ProjectStatus(name: 'foo', description: 'bar')
def user = new User(firstName: 'foo',
lastName: 'bar',
userName: 'baz',
email: 'q@w.com',
password: 'b' * 10)
def project = new Project(name: 'a new project',
status: projectStatus,
creationtime: new Date(),
totaltime: 0,
user: user)
expect: 'total time of the new project is 0 seconds'
project.totaltime == 0
when: 'adding a completed work session to the new project'
def duration = new Duration(seconds: 987)
def done = new WorkSessionStatus(name: 'done', description: 'done')
new WorkSession(starttime: new Date(),
duration: duration,
project: project,
status: done
.save(failOnError: true, flush: true)
// NOTE: a refresh is required to update the domain object
project.refresh()
then: 'total time is equal to duration of completed work session'
project.totaltime == 987
}
}