Grails 集成测试和事务

发布于 2024-10-01 16:02:23 字数 1524 浏览 3 评论 0原文

我不明白为什么这个集成测试失败。我可以通过删除服务方法上方的 @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 服务事务

I don't get why this integration test fails. I can get the test to pass by either removing the @Transactional(propagation = Propagation.REQUIRES_NEW) annotation above the service method, OR by setting transactional = false in the Integration Test

I realize that the integration test itself is running in a transaction, and that's why I've got the annotation on the service method.

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

and my service method is defined:

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

The Order entity is a simple domain object and I'm on Grails 1.2.1, MySQL 5.x (dialect=org.hibernate.dialect.MySQL5InnoDBDialect)

I've seen this related post, but still no cigar :(

Grails Service Transactions

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

烏雲後面有陽光 2024-10-08 16:02:23

嵌套内部事务提交的数据更改确实应该在父事务中立即可见。

而且我真的不知道为什么它们GroovyTestCase 的事务上下文。 其他人也不知道,并且正在使用与我类似的方法< /a>.

考虑以下测试用例。测试用例本身不是事务性的,但调用事务性方法。 - 这按预期工作。

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

最后,一些更一般的旁注,它不是 boolean transactional = true (尽管它似乎有效),而是 static transactional = true。您的集成测试还应该扩展 GroovyTestCase,而不是它的子类GrailsUnitTestCase,因为您不需要后者的模拟能力。 isActive 字段应命名为 active,这样,将按照命名约定自动生成 isActive() getter。

Data changes that a nested, inner, transaction had committed, should be, indeed, instantly visible in the parent transaction.

And I really don't know why they are not in the transactional context of a GroovyTestCase. Others don't know, as well, and are using similar approaches to mine.

Consider the following test case. The test case, itself, is not transactional, but calls a transactional method. - This works as expected.

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

Now consider the following, "normal", transactional, test case. Data changes from within the nested transaction are not visible in the parent transaction.

All I can say is, transactional test cases don't work with nested transactions, so use the non-transactional test case above.
If we don't understand the cause, we can, at least, know our options.

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

Not related to your primary question, but to your overall intent, here is a test case that checks whether the nested transaction is rolled back, properly:

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

Finally, some more general sidenotes, it's not boolean transactional = true (which appears to work, though), but static transactional = true. Your integration tests should also extend GroovyTestCase, not its subclass GrailsUnitTestCase, as you don't need the latter's mocking capabilities. The isActive field should be named active as then, an isActive() getter will be automatically generated by naming convention.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文