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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With