As a good developer
I should be able to unit test services that programmatically use transactions
So that I can minimize defects and have high unit test coverage
The Example Specs:
A persistent Foo object has a bar field which is of type String. Write a service with a doSomethingTransactional method that accepts a Foo object as a parameter. The method should change the value of bar to 'did something transactional' if the value of bar is NOT 'oops'
The Domain:
package com.henyo.model class Foo { String bar static constraints = { } }
The Unit Test:
package com.henyo.service import grails.test.* import com.henyo.model.* import com.henyo.service.* import com.henyo.* class FooServiceTests extends BaseUnitTestCase{ void testShouldCommit(){ def foo = new Foo(bar:'not oops') mockForTransaction(Foo) def service = new FooService() service.doSomethingTransactional(foo) } void testShouldRollBack(){ def foo = new Foo(bar:'oops') mockForTransaction(Foo,true) def service = new FooService() service.doSomethingTransactional(foo) } }
The unit test above takes into account 2 scenarios. One where we expect that the transaction will be committed and the other one will signal a rollback. The mockForTransaction actually returns a TransactionStatus object which can be used to check if the transaction is indeed committed or if it is for roll back. There is no need though to test for this as BaseUnitTestCase handles this for you automatically.
The Service:
package com.henyo.service import com.henyo.model.* class FooService { boolean transactional = false def doSomethingTransactional = { foo -> Foo.withTransaction{ status -> if(foo.bar == 'oops'){ status.setRollbackOnly() } } } }
My BaseUnitTestCase:
package com.henyo import grails.test.GrailsUnitTestCase import org.springframework.transaction.TransactionStatus class BaseUnitTestCase extends GrailsUnitTestCase { def statusControls protected void setUp() { super.setUp() statusControls = [] } protected void tearDown(){ statusControls.each{ it.verify() } statusControls.clear() super.tearDown() } def mockForTransaction(Class clazz,boolean expectRollback = false){ registerMetaClass(clazz) def statusControl = mockFor(TransactionStatus) statusControls << statusControl if(expectRollback) statusControl.demand.setRollbackOnly(1..1) { println 'setRollbackOnly called'} def status = statusControl.createMock() clazz.metaClass.'static'.withTransaction = { Closure callable -> callable.call(status) } return statusControl } }
Very helpful. Thanks!
ReplyDeleteIt just works!
You are welcome. Am glad to be of some help. Suggestions to improve on the process or the code is also welcome
ReplyDeletei've got error message when i run grails test-app
ReplyDeleteNo signature of method: static Foo.withTransaction() is applicable for argument types: (FooService$_closure1_closure2) values: [FooService$_closure1_closure2@1a5d6d6]
Which version of grails are you using?
ReplyDeletethank you for your reply :)
ReplyDeletemy grails version is grails 1.2.1
@B_CORPSE
ReplyDeleteI updated the sample code. It now works for grails 1.2.1.
Let me know if you want the skeletion grails project. I could zip it up and send it to you
truely thank you,can u send skeleton project to this email bcorpse(AT)gmail.com.
ReplyDeleteEverything has its value. Thanks for sharing this informative information with us. GOOD works! agen online
ReplyDelete