Grails 2.5.0 - Disabling autoTimestamp during Integration Tests

At some point between Grails 2.2 and 2.5, the functionality around the autoTimestamp flag has changed in a way that has caused it to not function during integration tests. What this means is that if you are testing integrations that involve the creation of a domain that has the autoTimestamp flag, you may be hit with not-nullable errors:

org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : com.your.package.YourDomain.dateCreated; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : com.your.package.YourDomain.dateCreated

Obviously the reason for this is that the dateCreated (and lastUpdated if you use it) field is not being set in the way that it would if the application was running.

If you are unable to manually add these in your test, (e.g. in my case the object was being created and saved by a service that was under test - no chance to mock or modify before the save), you might look to somehow disabling the autoTimeout and setting the default value An answer lies in Bootstrap.groovy, where we can catch the grails context and modify the domain class before the tests are executed.

Note that if you're developing a plugin, you wouldn't normally have a Bootstrap.groovy file, since its unlikely you're interacting with a database. But you can create it manually, and it won't get packaged up in your plugin build.

Bootstrap

In your `Bootstrap.groovy`, you'll need the following function. This will disable the `autoTimestamp`, and set the `dateCreated` and `lastUpdated` fields to the current date:
    def disableAutoTimestamp(Class domClass) {
    def grailsSave = domClass.metaClass.pickMethod('save',
        [Map] as Class[])

    domClass.metaClass.save = { Map params ->

        def m = new GrailsDomainBinder()
            .getMapping(delegate.getDomainClass())
        println("Disabling autoTimestamp for ${domClass.name}") 

        if (m?.autoTimestamp) m.autoTimestamp = false 

        def colList = [] 
        domClass.declaredFields.each { 
            if (!it.synthetic) 
                colList.add(it.name.toString()) 
        } 
        if (colList.contains("dateCreated")) { 
            println "Setting ${domClass.simpleName}.dateCreated to now" 
            domClass.metaClass.setProperty(delegate, "dateCreated",
                new Date(System.currentTimeMillis()))
                // Update this when using Java8
        } 

        if (colList.contains("lastUpdated")) { 
            println "Setting ${domClass.simpleName}.lastUpdated to now" 
            domClass.metaClass.setProperty(delegate, "lastUpdated",
                new Date(System.currentTimeMillis()))
        } 
        grailsSave.invoke(delegate, [params] as Object[]) 
    } 
} 

Note that prior to grails 2.4, the GrailsDomainBinder().getMapping() was static.

The stackoverflow question that I based this code on iterated through all classes with:

grailsApplication.domainClasses.each { gdc ->
    def domClass = gdc.clazz 
    ... 
} 

I found that this didn't work when proccessing all domains, so I specify them explicitly. The rest of the Bootstrap.groovy looks like this (Note the conditional check for Environment - Very important!)

import grails.util.Environment
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsDomainBinder 

class BootStrap { 

    def init = { servletContext ->
        if (Environment.current == Environment.TEST) { 
            disableAutoTimestamp(YourDomain.class) 
        } 
    } 

    def disableAutoTimestamp(Class domClass) { 
        ... 
    } 
} 

References

  1. http://stackoverflow.com/questions/28735133/pull-domain-mapping-in-bootstrap-and-modify-it-grails
  2. http://grails.github.io/grails-doc/2.5.0/ref/Database%20Mapping/autoTimestamp.html
  3. http://grails.1312388.n4.nabble.com/Grails-2-3-GrailsDomainBinder-getMapping-no-longer-static-tp4648984p4648994.html