覆盖 dateCreated 以在 Grails 中进行测试

发布于 2024-11-03 03:00:44 字数 1394 浏览 5 评论 0原文

有没有什么方法可以在不关闭自动时间戳的情况下覆盖域类中 dateCreated 字段的值?

我需要测试控制器,并且必须提供具有特定创建日期的特定域对象,但 GORM 似乎会覆盖我提供的值。

编辑

我的课程如下所示:

class Message {

    String content
    String title
    User author

    Date dateCreated
    Date lastUpdated

    static hasMany = [comments : Comment]

    static constraints = {
        content blank: false
        author nullable: false
        title nullable: false, blank: false
    }

    static mapping = {
        tablePerHierarchy false
        tablePerSubclass true
        content type: "text"
        sort dateCreated: 'desc'
    }
}

class BlogMessage extends Message{

    static belongsTo = [blog : Blog]

    static constraints = {
        blog nullable: false
    }

}

我正在使用控制台来缩短时间。我在 Victor 的方法中遇到的问题是,当我写:

Date someValidDate = new Date() - (20*365)

BlogMessage.metaClass.setDateCreated = {
            Date d ->            
            delegate.@dateCreated = someValidDate
}

我得到以下异常:

groovy.lang.MissingFieldException: No such field: dateCreated for class: pl.net.yuri.league.blog.BlogMessage

当我尝试

Message.metaClass.setDateCreated = {
                Date d ->            
                delegate.@dateCreated = someValidDate
}

脚本运行良好,但不幸的是 dateCreated 没有被改变。

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 have to provide specific domain objects with specific creation date but GORM seems to override values I provide.

Edit

My classes look like this:

class Message {

    String content
    String title
    User author

    Date dateCreated
    Date lastUpdated

    static hasMany = [comments : Comment]

    static constraints = {
        content blank: false
        author nullable: false
        title nullable: false, blank: false
    }

    static mapping = {
        tablePerHierarchy false
        tablePerSubclass true
        content type: "text"
        sort dateCreated: 'desc'
    }
}

class BlogMessage extends Message{

    static belongsTo = [blog : Blog]

    static constraints = {
        blog nullable: false
    }

}

I'm using console to shorten things up. The problem which I encountered with Victor's approach is, when I write:

Date someValidDate = new Date() - (20*365)

BlogMessage.metaClass.setDateCreated = {
            Date d ->            
            delegate.@dateCreated = someValidDate
}

I get following exception:

groovy.lang.MissingFieldException: No such field: dateCreated for class: pl.net.yuri.league.blog.BlogMessage

When I tried

Message.metaClass.setDateCreated = {
                Date d ->            
                delegate.@dateCreated = someValidDate
}

Script goes well, but unfortunately dateCreated is not being altered.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

森林迷了鹿 2024-11-10 03:00:44

我遇到了类似的问题,并且能够通过

  • 使用 BuildTestData 插件(无论如何我们经常使用它) 覆盖我的域的 dateCreated (在 Quartz Job 测试中,因此规范上没有 @TestFor 注释,Grails 2.1.0)太棒了)
  • 使用 save(flush:true) 双击域实例

作为参考,我的测试:

import grails.buildtestdata.mixin.Build
import spock.lang.Specification
import groovy.time.TimeCategory

@Build([MyDomain])
class MyJobSpec extends Specification {

    MyJob job

    def setup() {
        job = new MyJob()
    }

    void "test execute fires my service"() {
        given: 'mock service'
            MyService myService = Mock()
            job.myService = myService

        and: 'the domains required to fire the job'
            Date fortyMinutesAgo
            use(TimeCategory) {
                fortyMinutesAgo = 40.minutes.ago
            }

            MyDomain myDomain = MyDomain.build(stringProperty: 'value')
            myDomain.save(flush: true) // save once, let it write dateCreated as it pleases
            myDomain.dateCreated = fortyMinutesAgo
            myDomain.save(flush: true) // on the double tap we can now persist dateCreated changes

        when: 'job is executed'
            job.execute()

        then: 'my service should be called'
            1 * myService.someMethod()
    }
}

I was having a similar issue, and was able to overwrite dateCreated for my domain (in a Quartz Job test, so no @TestFor annotation on the Spec, Grails 2.1.0) by

  • Using the BuildTestData plugin (which we use regularly anyway, it is fantastic)
  • Double-tapping the domain instance with save(flush:true)

For reference, my test:

import grails.buildtestdata.mixin.Build
import spock.lang.Specification
import groovy.time.TimeCategory

@Build([MyDomain])
class MyJobSpec extends Specification {

    MyJob job

    def setup() {
        job = new MyJob()
    }

    void "test execute fires my service"() {
        given: 'mock service'
            MyService myService = Mock()
            job.myService = myService

        and: 'the domains required to fire the job'
            Date fortyMinutesAgo
            use(TimeCategory) {
                fortyMinutesAgo = 40.minutes.ago
            }

            MyDomain myDomain = MyDomain.build(stringProperty: 'value')
            myDomain.save(flush: true) // save once, let it write dateCreated as it pleases
            myDomain.dateCreated = fortyMinutesAgo
            myDomain.save(flush: true) // on the double tap we can now persist dateCreated changes

        when: 'job is executed'
            job.execute()

        then: 'my service should be called'
            1 * myService.someMethod()
    }
}
情栀口红 2024-11-10 03:00:44

掌握 ClosureEventListener 允许您暂时禁用 grails 时间戳。

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
    }
}

可能有一种更简单的方法来获取 applicationContext 或 Hibernate 配置,但这在运行应用程序时对我有用。它在集成测试中不起作用,如果有人知道如何做到这一点,请告诉我。

更新

对于 Grails 2 使用 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
}

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
}
山川志 2024-11-10 03:00:44

我通过简单地设置字段就得到了这个工作。诀窍是在首先保存域对象之后执行此操作。我假设 dateCreated 时间戳是在保存时设置的,而不是在对象创建时设置的。

沿着这些思路

class Message {
  String content
  Date dateCreated
}

// ... and in test class

def yesterday = new Date() - 1
def m = new Message( content: 'hello world' )
m.save( flush: true )
m.dateCreated = yesterday
m.save( flush: true )

使用 Grails 2.3.6

I got this working by simply setting the field. The trick was to do that after the domain object has been saved first. I assume that the dateCreated timestamp is set on save and not on object creation.

Something along these lines

class Message {
  String content
  Date dateCreated
}

// ... and in test class

def yesterday = new Date() - 1
def m = new Message( content: 'hello world' )
m.save( flush: true )
m.dateCreated = yesterday
m.save( flush: true )

Using Grails 2.3.6

那伤。 2024-11-10 03:00:44

从 Grails 3 和 GORM 6 开始,您可以利用 AutoTimestampEventListener 来执行暂时忽略所有或选择时间戳的 Runnable

以下是我在集成测试中使用的一个小片段,这是必要的:

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

    listener.withoutTimestamps(domainClass, closure)
}

那么在您的情况下,您可以执行以下操作:

executeWithoutTimestamps(BlogMessage, {
    Date someValidDate = new Date() - (20*365)
    BlogMessage message = new BlogMessage()
    message.dateCreated = someValidDate
    message.save(flush: true)
})

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)
})
巴黎盛开的樱花 2024-11-10 03:00:44

我正在使用类似的东西进行初始导入/迁移。

以 gabe 的帖子作为入门(这对我的 Grails 2.0 不起作用),并查看 Grails 1.3.7 中 ClosureEventTriggeringInterceptor 的旧源代码,我想出了这个:

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

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)
    }
}
停顿的约定 2024-11-10 03:00:44

您可以尝试通过在域类映射中设置 autoTimestamp = false 来禁用它。我对全局覆盖表示怀疑,因为该值是直接从 System.currentTimeMillis() 获取的(我正在查看 org.codehaus.groovy.grails.orm。 hibernate.support.ClosureEventListener.java)。

因此,我只能建议您重写类中 dateCreated 字段的设置器,并分配您自己的值。也许甚至元类访问也可以工作,比如

Date stubDateCreated
...
myDomainClass.metaClass.setDateCreated = 
    { Date d -> delegate.@dateCreated = stubDateCreated }

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 }
凉栀 2024-11-10 03:00:44

我无法使上述技术发挥作用,对 GrailsDomainBinder.getMapping 的调用始终返回 null???

但是...

您可以使用固定插件在域实例上设置 dateCreated 属性

初始加载不会执行此操作...

fixture {
    // saves to db, but date is set as current date :(
    tryDate( SomeDomain, dateCreated: Date.parse( 'yyyy-MM-dd', '2011-12-25') )
}

但是如果您使用后处理程序

post {
    // updates the date in the database :D
    tryDate.dateCreated = Date.parse( 'yyyy-MM-dd', '2011-12-01')
}

此处的装置文档的相关部分

AFAIK装置不适用于单元测试,尽管插件作者将来可能会添加单元测试支持。

I couldn't get the above techniques to work, the call to GrailsDomainBinder.getMapping always returned null???

However...

You can use the fixtures plugin to set the dateCreated property on a domain instance

The initial loading will not do it...

fixture {
    // saves to db, but date is set as current date :(
    tryDate( SomeDomain, dateCreated: Date.parse( 'yyyy-MM-dd', '2011-12-25') )
}

but if you follow up with a post handler

post {
    // updates the date in the database :D
    tryDate.dateCreated = Date.parse( 'yyyy-MM-dd', '2011-12-01')
}

Relevant part of the fixtures docs here

AFAIK fixtures don't work for unit testing, although the plugin authors may add unit testing support in the future.

执妄 2024-11-10 03:00:44

一个更简单的解决方案是在集成测试中使用 SQL 查询,在使用所需的其他值初始化对象后根据需要进行设置。

YourDomainClass.executeUpdate(
"""UPDATE YourDomainClass SET dateCreated = :date
WHERE yourColumn = :something""",
[date:yourDate, something: yourThing])

A simpler solution is to use a SQL query in your integration test to set it as you please after you initialize your object with the other values you want.

YourDomainClass.executeUpdate(
"""UPDATE YourDomainClass SET dateCreated = :date
WHERE yourColumn = :something""",
[date:yourDate, something: yourThing])
呆橘 2024-11-10 03:00:44

从 grails 2.5.1 开始,GrailsDomainBinder 类的 getMapping() 方法不是静态的,上述方法都不能按原样工作。然而,@Volt0 的方法只需稍加调整即可工作。由于我们所有人都试图这样做以使我们的测试正常工作,所以我没有将其放置在 BootStrap 中,而是将其放置在实际的集成测试中。这是我对 Volt0 方法的调整:

def disableAutoTimestamp(Class domainClass) {
    Mapping mapping = new GrailsDomainBinder().getMapping(domainClass)
    mapping.autoTimestamp = false
}

def enableAutoTimestamp(Class domainClass) {
    Mapping mapping = new GrailsDomainBinder().getMapping(domainClass)
    mapping.autoTimestamp = true
}

并且只需在测试中调用这些方法,例如

disableAutoTimestamp(Domain.class)
//Your DB calls
enableAutoTimestamp(Domain.class)

上面的代码也可以放置在 src 目录中,并且可以在测试中调用,但是我将其放置在实际测试中,因为我的应用程序中只有一个类需要这个。

As of grails 2.5.1, getMapping() method of GrailsDomainBinder class is not static,non of the above method works as is. However, @Volt0's method works with minor tweaking. Since all of us are trying to do so to make our tests working, instead of placing it in BootStrap, I placed it in actual integration test. Here is my tweak to Volt0's method:

def disableAutoTimestamp(Class domainClass) {
    Mapping mapping = new GrailsDomainBinder().getMapping(domainClass)
    mapping.autoTimestamp = false
}

def enableAutoTimestamp(Class domainClass) {
    Mapping mapping = new GrailsDomainBinder().getMapping(domainClass)
    mapping.autoTimestamp = true
}

And simply call these methods in tests like

disableAutoTimestamp(Domain.class)
//Your DB calls
enableAutoTimestamp(Domain.class)

The above code can also be placed in src directory and can be called in tests however I placed this in actual test as there was only one class in my app where I needed this.

追我者格杀勿论 2024-11-10 03:00:44

简单的解决方案是添加映射:

static mapping = {
    cache true
    autoTimestamp false
}

The easy solution is to add a mapping:

static mapping = {
    cache true
    autoTimestamp false
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文