Unit test Grails GORM’s formulas

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

        then: 'total time is equal to duration of completed work session'
        project.totaltime == 987