Grails integration tests and transactions
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)
开发者_JAVA技巧 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
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.
精彩评论