Переопределение даты Создано для тестирования в Grails - PullRequest
18 голосов
/ 20 апреля 2011

Можно ли как-нибудь переопределить значение поля 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
    }

}

Я использую консоль, чтобы сократить время.Проблема, с которой я столкнулся при подходе Виктора, заключается в следующем:

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
}

Scriptидет хорошо, но, к сожалению, dateCreated не изменяется.

Ответы [ 10 ]

7 голосов
/ 24 октября 2014

У меня была похожая проблема, и я смог перезаписать dateCreated для моего домена (в тесте Quartz Job, поэтому нет аннотации @TestFor в Spec, Grails 2.1.0) на

  • Используя плагин BuildTestData (который мы все равно используем регулярно, это просто фантастика)
  • Двойное нажатие на экземпляр домена с помощью 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()
    }
}
6 голосов
/ 28 июля 2011

Удержание 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
}
5 голосов
/ 21 марта 2015

Я получил это, просто установив поле. Хитрость заключалась в том, чтобы сделать это после того, как объект домена был сохранен первым. Я предполагаю, что отметка времени 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

2 голосов
/ 10 апреля 2018

Начиная с 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)
})
2 голосов
/ 27 января 2012

Я использую что-то вроде этого для первоначального импорта / миграции.

Взяв сообщение Гейба как стартер (который не работал для меня, Grails 2.0) и взглянув на старый исходный код ClosureEventTriggeringInterceptor в Grails 1.3.7, я придумал следующее:

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)
    }
}
1 голос
/ 26 мая 2014

Более простое решение - использовать SQL-запрос в тесте интеграции, чтобы установить его по своему усмотрению после инициализации объекта другими значениями, которые вы хотите.

YourDomainClass.executeUpdate(
"""UPDATE YourDomainClass SET dateCreated = :date
WHERE yourColumn = :something""",
[date:yourDate, something: yourThing])
1 голос
/ 12 июля 2012

Мне не удалось заставить работать описанные выше методы, вызов GrailsDomainBinder.getMapping всегда возвращал null ???

Однако ...

Вы можете использовать плагин fixtures, чтобы установить свойство 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 не работают для модульного тестирования, хотя авторы плагинов могут добавить поддержку модульного тестирования в будущем.

1 голос
/ 21 апреля 2011

Вы можете попытаться отключить его, установив autoTimestamp = false в сопоставлении классов домена. Я сомневаюсь в глобальном переопределении, поскольку значение берется непосредственно из System.currentTimeMillis() (я смотрю на org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventListener.java ).

Поэтому я могу только предложить переопределить сеттер для поля dateCreated в своем классе и назначить свое собственное значение. Может быть, даже доступ к метаклассу будет работать, как

Date stubDateCreated
...
myDomainClass.metaClass.setDateCreated = 
    { Date d -> delegate.@dateCreated = stubDateCreated }
0 голосов
/ 02 мая 2016

Начиная с версии 2.5.1, метод getMapping () класса GrailsDomainBinder не является статическим, ни один из приведенных выше методов не работает как есть.Однако метод @ 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 и вызвать в тестаходнако я поместил это в настоящий тест, так как в моем приложении был только один класс, где мне это было нужно.

0 голосов
/ 06 мая 2012

Простое решение - добавить отображение:

static mapping = {
    cache true
    autoTimestamp false
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...