outside - grails views
Anulando fechaCreado para pruebas en Grails (10)
¿Hay alguna manera de que pueda anular el valor del campo dateCreated
en mi clase de dominio sin desactivar la marca de tiempo automática?
Necesito probar el controlador y debo proporcionar objetos de dominio específicos con fecha de creación específica, pero GORM parece anular los valores que proporciono.
Editar
Mis clases se ven así:
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
}
}
Estoy usando la consola para acortar las cosas. El problema que encontré con el enfoque de Victor es, cuando escribo:
Date someValidDate = new Date() - (20*365)
BlogMessage.metaClass.setDateCreated = {
Date d ->
delegate.@dateCreated = someValidDate
}
Me sale la siguiente excepción:
groovy.lang.MissingFieldException: No such field: dateCreated for class: pl.net.yuri.league.blog.BlogMessage
Cuando lo intente
Message.metaClass.setDateCreated = {
Date d ->
delegate.@dateCreated = someValidDate
}
El script va bien, pero desafortunadamente dateCreated
no se está modificando.
A partir de Grails 2.5.1, el método getMapping () de la clase GrailsDomainBinder no es estático, ninguno de los métodos anteriores funciona como está. Sin embargo, el método de @ Volt0 funciona con ajustes menores. Ya que todos estamos intentando hacerlo para que nuestras pruebas funcionen, en lugar de colocarlo en BootStrap, lo puse en una prueba de integración real. Aquí está mi ajuste al método de 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
}
Y simplemente llamar a estos métodos en pruebas como
disableAutoTimestamp(Domain.class)
//Your DB calls
enableAutoTimestamp(Domain.class)
El código anterior también se puede colocar en el directorio src y se puede llamar en las pruebas; sin embargo, lo puse en una prueba real, ya que solo había una clase en mi aplicación donde la necesitaba.
A partir de Grails 3 y GORM 6, puedes aprovechar AutoTimestampEventListener
para ejecutar un Runnable
que ignora temporalmente todos o selecciona marcas de tiempo.
El siguiente es un pequeño fragmento de código que uso en mis pruebas de integración donde es necesario:
void executeWithoutTimestamps(Class domainClass, Closure closure){
ApplicationContext applicationContext = Holders.findApplicationContext()
HibernateDatastore mainBean = applicationContext.getBean(HibernateDatastore)
AutoTimestampEventListener listener = mainBean.getAutoTimestampEventListener()
listener.withoutTimestamps(domainClass, closure)
}
Entonces en tu caso podrías hacer lo siguiente:
executeWithoutTimestamps(BlogMessage, {
Date someValidDate = new Date() - (20*365)
BlogMessage message = new BlogMessage()
message.dateCreated = someValidDate
message.save(flush: true)
})
Conseguí este trabajo simplemente estableciendo el campo. El truco era hacerlo después de que el objeto de dominio se haya guardado primero. Supongo que la fecha de fecha y hora creada se establece en guardar y no en la creación de objetos.
Algo en este sentido
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 )
Usando Grails 2.3.6
Estaba teniendo un problema similar, y pude sobrescribir dateCreated para mi dominio (en una prueba de trabajo de cuarzo, por lo que no hay una anotación de @TestFor en el Spec, Grails 2.1.0) por
- Usar el complemento BuildTestData (que usamos regularmente de todos modos, es fantástico)
- Doble toque en la instancia de dominio con guardar (flush: true)
Para referencia, mi prueba:
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()
}
}
Estoy usando algo como esto para una importación / migración inicial.
Tomando la publicación de gabe como titular (que no me funcionó con Grails 2.0) y mirando el código fuente antiguo de ClosureEvent TriggeringInterceptor en Grails 1.3.7, se me ocurrió esto:
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)
}
}
La solución fácil es agregar un mapeo:
static mapping = {
cache true
autoTimestamp false
}
No pude hacer funcionar las técnicas anteriores, la llamada a GrailsDomainBinder.getMapping siempre devolvió el valor nulo.
Sin embargo...
Puede usar el complemento de accesorios para establecer la propiedad dateCreated en una instancia de dominio
La carga inicial no lo hará ...
fixture {
// saves to db, but date is set as current date :(
tryDate( SomeDomain, dateCreated: Date.parse( ''yyyy-MM-dd'', ''2011-12-25'') )
}
pero si sigues con un controlador de correos
post {
// updates the date in the database :D
tryDate.dateCreated = Date.parse( ''yyyy-MM-dd'', ''2011-12-01'')
}
Parte relevante de los documentos de accesorios aquí
Los dispositivos AFAIK no funcionan para pruebas de unidad, aunque los autores del complemento pueden agregar soporte de prueba de unidad en el futuro.
Obtener una retención de ClosureEventListener le permite deshabilitar temporalmente la marca de tiempo de 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
}
}
Puede haber una forma más fácil de obtener la configuración de ApplicationContext o Hibernate, pero eso me funcionó cuando ejecuté la aplicación. No funciona en una prueba de integración, si alguien descubre cómo hacerlo, hágamelo saber.
Actualizar
Para 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
}
Puede intentar desactivarlo configurando autoTimestamp = false
en la asignación de clase de dominio. Dudo acerca de la invalidación global porque el valor se toma directamente de System.currentTimeMillis()
(estoy viendo org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventListener.java ).
Por lo tanto, solo puedo sugerir que anule un setter para el campo dateCreated
en su clase y asigne su propio valor. Tal vez incluso el acceso a la metaclase funcione, como
Date stubDateCreated
...
myDomainClass.metaClass.setDateCreated =
{ Date d -> delegate.@dateCreated = stubDateCreated }
Una solución más sencilla es utilizar una consulta SQL en su prueba de integración para configurarlo como desee después de inicializar su objeto con los otros valores que desee.
YourDomainClass.executeUpdate(
"""UPDATE YourDomainClass SET dateCreated = :date
WHERE yourColumn = :something""",
[date:yourDate, something: yourThing])