Интеграционные тесты и транзакции Grails - PullRequest
6 голосов
/ 10 ноября 2010

Я не понимаю, почему этот интеграционный тест не пройден. Я могу пройти тест, либо удалив аннотацию @Transactional(propagation = Propagation.REQUIRES_NEW) над методом обслуживания, либо установив transactional = false в Интеграционном тесте

Я понимаю, что сам интеграционный тест выполняется в транзакции, и поэтому я получил аннотацию к методу сервиса.

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
    }
}

и мой метод обслуживания определен:

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'}"
    }
}}

Сущность Order - это простой объект домена, и я нахожусь на Grails 1.2.1, MySQL 5.x (dialect = org.hibernate.dialect.MySQL5InnoDBDialect)

Я видел этот пост, но сигары до сих пор нет: (

Операции с Grails

1 Ответ

8 голосов
/ 10 ноября 2010

Изменения данных, совершенные вложенной внутренней транзакцией, действительно должны быть немедленно видны в родительской транзакции.

И я действительно не знаю, почему они не в транзакционном контексте GroovyTestCase. Другие тоже не знают и используют аналогичные подходы к моим .

Рассмотрим следующий контрольный пример. Сам тестовый случай не транзакционный, но вызывает транзакционный метод. - Это работает, как ожидалось.

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
        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
}

Теперь рассмотрим следующий, "нормальный", транзакционный, контрольный пример. Изменения данных из вложенной транзакции не видны в родительской транзакции.

Все, что я могу сказать, это то, что транзакционные тестовые примеры не работают с вложенными транзакциями, поэтому используйте нетранзакционный тестовый пример выше .
Если мы не понимаем причину, мы можем, по крайней мере, знать наши варианты.

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();
    }
}

Не связанный с вашим основным вопросом, но с вашими общими намерениями, вот тестовый пример, который проверяет, правильно ли откатана вложенная транзакция:

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
        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
}

Наконец, некоторые более общие sidenotes, это не boolean transactional = true (что, похоже, работает), но static transactional = true. Ваши интеграционные тесты также должны extend GroovyTestCase, а не его подкласс GrailsUnitTestCase, так как вам не нужны его возможности для насмешек. Поле isActive должно быть названо active, тогда получатель isActive() будет автоматически создан в соответствии с соглашением об именах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...