5

No entiendo por qué falla esta prueba de integración. Puedo hacer pasar la prueba eliminando la anotación @Transactional(propagation = Propagation.REQUIRES_NEW) sobre el método de servicio, O configurando transactional = false en la Prueba de integraciónGrails pruebas y transacciones 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 mi servicio método se define:

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 (dialecto = org.hibernate. dialect.MySQL5InnoDBDialect)

que he visto este post relacionado, pero todavía no puro :(

Grails Service Transactions

Respuesta

8

Los cambios de datos que una transacción anidada e interna había cometido deberían, de hecho, estar visibles de manera instantánea en la transacción padre.

Y realmente no sé por qué son no en el contexto transaccional de un GroovyTestCase. Others don't know, as well, and are using similar approaches to mine.

Considere el siguiente caso de prueba. El caso de prueba, en sí mismo, es no transaccional, pero invoca 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 
     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 
    } 
} 

Considere lo siguiente, "normal", transaccional, caso de prueba. Los cambios de datos desde dentro de la transacción anidada son no visibles en la transacción padre.

Todo lo que puedo decir es, los casos de prueba transaccionales no funcionan con transacciones anidadas, por lo utilizan el caso de prueba no transaccional por encima de.
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 relacionadas con su pregunta principal, pero a su intención general, aquí es un caso de prueba que comprueba si la transacción anidada se deshace, adecuadamente:

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. Sus pruebas de integración también deberían extendGroovyTestCase, no su subclase GrailsUnitTestCase, ya que no necesita las capacidades de burla de este último. El campo isActive debe llamarse active, entonces se generará automáticamente un getter isActive() al nombrar la convención.

+0

gracias, por lo que parece que esto es un error con el marco de prueba de integración. es decir, el código/lógica es correcto y funciona en una aplicación en ejecución, pero no en la prueba de integración.Tal vez debería JIRA – Sunny

+0

No creo que sea un error (el problema es demasiado central/prominente) sino un error de diseño en su lugar. - Si debe presentar un problema JIRA (con el marco Spring, no Grails), deje una nota en este lugar. – robbbert

+0

Una corrección en su ejemplo final (¿desactualizado?): 'Propagation.REQUIRES_NEW' comienza una nueva transacción, _unrelated and independent_. Por el contrario, una transacción _nested_ (visible para el padre y con su propio punto de guardado) ocurre bajo 'Propagation.NESTED'. Los Javadocs de Spring hacen un gran trabajo al explicar la diferencia: http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/annotation/Propagation.html –

Cuestiones relacionadas