Подтвердить что ты не робот

Переопределение датыСоздано для тестирования в Grails

Можно ли каким-либо образом переопределить значение поля 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 ->            
            [email protected] = someValidDate
}

Я получаю следующее исключение:

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

Когда я попробовал

Message.metaClass.setDateCreated = {
                Date d ->            
                [email protected] = someValidDate
}

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

4b9b3361

Ответ 1

Захват 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
}

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

Взяв gabe post в качестве стартера (который не работал у меня 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)
    }
}

Ответ 5

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

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

Date stubDateCreated
...
myDomainClass.metaClass.setDateCreated = 
    { Date d -> [email protected] = stubDateCreated }

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

Ответ 9

Простым решением является добавление отображения:

static mapping = {
    cache true
    autoTimestamp false
}