Pregunta Pruebas de integración de grails y transacciones.


No entiendo por qué falla esta prueba de integración. Puedo hacer que la prueba pase ya sea eliminando el @Transactional(propagation = Propagation.REQUIRES_NEW) anotación sobre el método de servicio, O mediante la configuración transactional = false en la prueba de integración

Me doy cuenta de que la prueba de integración se está ejecutando en una transacción, y es por eso que tengo la anotación en el método de servicio.

class DbTests extends GrailsUnitTestCase {

boolean transactional = true
def customerService

void testTransactionsCommit() {
    def orderIds = [1, 2, 3]
    orderIds.each  { // lets make sure they all start out as Active
        def order = Order.get(it)
        order.isActive = true
        order.save(flush:true, validate:true, failOnError: true)
    }

    customerService.cancelOrders(orderIds)

    orderIds.each  {
        def order = Order.get(it).refresh()
        assertEquals false, order.isActive
    }
}

y se define mi método de servicio:

class CustomerService {

boolean transactional = true
@Transactional(propagation = Propagation.REQUIRES_NEW)
def cancelOrders(def orderIds) {
    orderIds.each {
        Order order = Order.get(it)
        if(order.id == 5) //
            throw new RuntimeException('Simulating an exception here, panic!')
        order.isActive = false
        order.save(flush:true, validate:true, failOnError: true)
        println "Order.id = $order.id is ${order.isActive? 'ACTIVE' : 'CANCELLED'}"
    }
}}

La entidad Order es un objeto de dominio simple y estoy en Grails 1.2.1, MySQL 5.x (dialect = org.hibernate.dialect.MySQL5InnoDBDialect)

He visto esta publicación relacionada, pero todavía no hay cigarro :(

Servicio de Grails Transacciones


5
2017-11-10 05:49


origen


Respuestas:


Los cambios de datos que una transacción anidada e interna había cometido deberían, de hecho, ser visibles al instante en la transacción principal. 

Y realmente no sé por qué son no en el contexto transaccional de una GroovyTestCase. Otros no saben, también, y están utilizando enfoques similares a los míos.

Considere el siguiente caso de prueba. El caso de prueba, en sí, es no Transaccional, pero llama a un método transaccional. - Esto funciona como se esperaba.

class TransactionalMethodTest extends GroovyTestCase {
    static transactional = false // test case is not transactional
    def customerService

    void testTransactionsCommit() {
        // start a new transaction, 
        // setting order 1 inactive
        setOrderInactive()
        assert ! Order.get(1).isActive
    }

    @Transactional(propagation = Propagation.REQUIRED)
    private void setOrderInactive() {
        // make sure that order 1 is active
        Order order = Order.get(1)
        order.isActive = true
        order.save(flush:true)

        assert Order.get(1).isActive

        // the following method acts in isolation level
        // Propagation.REQUIRES_NEW, which means,
        // a new, nested, transaction is started
        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        customerService.cancelOrders([1])

        // changes from the nested transaction are
        // visible, instantly
        assert ! Order.get(1).isActive
        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
}

Ahora considere lo siguiente, "normal", transaccional, caso de prueba. Los cambios de datos desde dentro de la transacción anidada son no visible en la transacción principal.

Todo lo que puedo decir es que los casos de prueba transaccional no funcionan con transacciones anidadas, por lo que utilizar el caso de prueba no transaccional anterior.
Si no entendemos la causa, podemos, al menos, conocer nuestras opciones.

class TransactionalTestCaseTests extends GroovyTestCase {
    static transactional = true // default; Propagation.REQUIRED
    def customerService

    void testTransactionsCommit() {
        // make sure that order 1 is active
        Order order = Order.get(1)
        order.isActive = true
        order.save(flush:true)

        assert Order.get(1).isActive

        // the following method acts in isolation level
        // Propagation.REQUIRES_NEW, which means,
        // a new, nested, transaction is started
        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        customerService.cancelOrders([1])

        // the changes from the inner transaction
        // are not yet visible
        assert Order.get(1).isActive
        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    @Override
    protected void tearDown() throws Exception {
        // the changes from the inner transaction
        // are still not visible
        assert Order.get(1).isActive

        super.tearDown();
    }
}

No relacionado con su pregunta principal, sino con su intención general, aquí hay un caso de prueba que verifica si la transacción anidada se revierte correctamente:

class NestedTransactionRolledBackTests extends GroovyTestCase {
    static transactional = false // test case is not transactional
    def customerService

    void testTransactionsCommit() {
        // start a new transaction, 
        // setting order 1 active
        setOrderActive()
        assert Order.get(1).isActive
    }

    @Transactional(propagation = Propagation.REQUIRED)
    private void setOrderActive() {
        // make sure that order 1 is active
        Order order = Order.get(1)
        order.isActive = true
        order.save(flush:true)

        assert Order.get(1).isActive

        // the following method acts in isolation level
        // Propagation.REQUIRES_NEW, which means,
        // a new, nested, transaction is started.
        // This transaction will fail, and be rolled back.
        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        shouldFail(NullPointerException) {
            customerService.cancelOrders([1, -999])
        }

        // changes from the nested transaction are
        // visible, instantly.
            // The changes have been rolled back
        assert Order.get(1).isActive
        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
}

Finalmente, algunas notas al margen más generales, no es boolean transactional = true (que parece funcionar, sin embargo), pero static transactional = true. Tu integración pruebas también deben extend  GroovyTestCase, no su subclase GrailsUnitTestCase, ya que no necesitas las capacidades de burla de este último. los isActive campo debe ser nombrado active como entonces, un isActive() getter se generará automáticamente por convención de nomenclatura.


8
2017-11-10 11:32