Overriding dateCreated for testing in Grails

后端 未结 10 1418
日久生厌
日久生厌 2021-02-12 12:27

Is there any way I can override the value of dateCreated field in my domain class without turning off auto timestamping?

I need to test controller and I ha

相关标签:
10条回答
  • 2021-02-12 13:20

    As of Grails 3 and GORM 6 you can tap into AutoTimestampEventListener to execute a Runnable that temporarily ignores all or select timestamps.

    The following is a small snippet I use in my integration tests where this is necessary:

    void executeWithoutTimestamps(Class domainClass, Closure closure){
        ApplicationContext applicationContext = Holders.findApplicationContext()
        HibernateDatastore mainBean = applicationContext.getBean(HibernateDatastore)
        AutoTimestampEventListener listener = mainBean.getAutoTimestampEventListener()
    
        listener.withoutTimestamps(domainClass, closure)
    }
    

    Then in your case you could do the following:

    executeWithoutTimestamps(BlogMessage, {
        Date someValidDate = new Date() - (20*365)
        BlogMessage message = new BlogMessage()
        message.dateCreated = someValidDate
        message.save(flush: true)
    })
    
    0 讨论(0)
  • 2021-02-12 13:23

    Getting a hold of the ClosureEventListener allows you to temporarily disable grails timestamping.

    import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes
    import org.codehaus.groovy.grails.commons.spring.GrailsWebApplicationContext
    import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration
    import org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor
    import org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventListener
    
    class FluxCapacitorController {
    
        def backToFuture = {
            changeTimestamping(new Message(), false)
            Message m = new Message()
            m.dateCreated = new Date("11/5/1955")
            m.save(failOnError: true)
            changeTimestamping(new Message(), true)
        }
    
        private void changeTimestamping(Object domainObjectInstance, boolean shouldTimestamp) {
            GrailsWebApplicationContext applicationContext = servletContext.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT)
            GrailsAnnotationConfiguration configuration = applicationContext.getBean("&sessionFactory").configuration
            ClosureEventTriggeringInterceptor interceptor = configuration.getEventListeners().saveOrUpdateEventListeners[0]
            ClosureEventListener listener = interceptor.findEventListener(domainObjectInstance)
            listener.shouldTimestamp = shouldTimestamp
        }
    }
    

    There may be an easier way to get the applicationContext or Hibernate configuration but that worked for me when running the app. It does not work in an integration test, if anyone figures out how to do that let me know.

    Update

    For Grails 2 use eventTriggeringInterceptor

    private void changeTimestamping(Object domainObjectInstance, boolean shouldTimestamp) {
        GrailsWebApplicationContext applicationContext = servletContext.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT)
        ClosureEventTriggeringInterceptor closureInterceptor = applicationContext.getBean("eventTriggeringInterceptor")
        HibernateDatastore datastore = closureInterceptor.datastores.values().iterator().next()
        EventTriggeringInterceptor interceptor = datastore.getEventTriggeringInterceptor()
    
        ClosureEventListener listener = interceptor.findEventListener(domainObjectInstance)
        listener.shouldTimestamp = shouldTimestamp
    }
    
    0 讨论(0)
  • 2021-02-12 13:23

    You can try to disable it by setting autoTimestamp = false in the domain class mapping. I doubt about global overriding because the value is taken directly from System.currentTimeMillis() (I'm looking at org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventListener.java).

    So I can only suggest that you override a setter for dateCreated field in your class, and assign your own value. Maybe even metaclass access will work, like

    Date stubDateCreated
    ...
    myDomainClass.metaClass.setDateCreated = 
        { Date d -> delegate.@dateCreated = stubDateCreated }
    
    0 讨论(0)
  • 2021-02-12 13:25

    I'm using something like this for an initial import/migration.

    Taking gabe's post as a starter (which didn't work for me Grails 2.0), and looking at the old source code for ClosureEventTriggeringInterceptor in Grails 1.3.7, I came up with this:

    class BootStrap {
    
        private void changeTimestamping(Object domainObjectInstance, boolean shouldTimestamp) {
            Mapping m = GrailsDomainBinder.getMapping(domainObjectInstance.getClass())
            m.autoTimestamp = shouldTimestamp
        }
    
        def init = { servletContext ->
    
            changeTimestamping(new Message(), false)
    
            def fooMessage = new Message()
            fooMessage.dateCreated = new Date("11/5/1955")
            fooMessage.lastUpdated = new Date()
            fooMessage.save(failOnError, true)
    
            changeTimestamping(new Message(), true)
        }
    }
    
    0 讨论(0)
提交回复
热议问题