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) {
...
}
}