I found that using Groovy's MockFor is just as convenient and does not contain this shortcoming.
Concept Overview
Often a developer would like to use mocks to isolate code and verify that a unit of code is operating correctly. The rationale is: "Given that collaborators are behaving in a specific way, the code under test behaves as expected." In other words, if collaborator returns A, then the code under test will perform B. If the collaborator returns C, then the code under test will perform D.
Many Java mock frameworks provide this ability, including EasyMock and JMock, to name a few.
mockFor Shortcoming
On a grails project, I wanted a mock to return a certain value. This did not work. Let me provide an example to illustrate the problem.
Let's say I have a piece of code named AccountService that checks an account balance. When the account balance is below $25, its raises an alert to the screen and returns false. If the balance is above $25 it proceeds for processing. AccountService depends on BalanceService. BalanceService checks the balance of the Account.
package com.solutionsfit
class AccountService {
def balanceService
def process() {
if (balanceService.checkBalance() < 25) {
raiseAlert()
return false
} else {
// process account normally.
}
return true
}
def raiseAlert() {
println "Balance is below \$25!"
}
}
package com.solutionsfit
class BalanceService {
def checkBalance() {
// checks the balance of the account.
}
}
I want to mock the balance service with mockFor in grails unit testing. I want achieve two things: 1) verify checkBalance was called once 2) manipulate the return value of checkBalance so I can test accountService.process(). Here is the test case using mockFor. We get this error:
groovy.lang.GroovyRuntimeException: Cannot compare com.solutionsfit.AccountServiceTests$_testProcess_accountIsTooLow_closure1 with value 'com.solutionsfit.AccountServiceTests$_testProcess_accountIsTooLow_closure1@ff9053' and java.lang.Integer with value '25'
at com.solutionsfit.AccountService.process(AccountService.groovy:8):8)
package com.solutionsfit
import grails.test.*
class AccountServiceTests extends GrailsUnitTestCase {
AccountService accountService = new AccountService()
protected void setUp() {
super.setUp()
}
protected void tearDown() {
super.tearDown()
}
void testProcess_accountIsTooLow() {
// create the mock control and set as collaborator in accountService
def mockBalanceServiceControl= mockFor(BalanceService)
accountService.balanceService = mockBalanceServiceControl.createMock()
// set expectation and give a mock return value
mockBalanceServiceControl.demand.checkBalance(1..1) { -> return 24 }
def result = accountService.process()
assertFalse("process should return false due to low balance", result)
mockBalanceServiceControl.verify()
}
}
Using MockForI can implement a correct test by using Groovy's native MockFor implementation. The syntax is slightly different but the test runs as expected. Here is an example of the test class.
package com.solutionsfit
import grails.test.*
import groovy.mock.interceptor.MockFor
class AccountServiceTests extends GrailsUnitTestCase {
AccountService accountService = new AccountService()
protected void setUp() {
super.setUp()
}
protected void tearDown() {
super.tearDown()
}
void testProcess_accountIsTooLow() {
// create the mock control and set as collaborator in accountService
def mockContext = new MockFor(BalanceService)
// set expectation and give a mock return value
mockContext.demand.checkBalance(1..1) { -> return 24 }
def mockBalanceService = mockContext.proxyInstance()
accountService.balanceService = mockBalanceService
def result = accountService.process()
assertFalse("process should return false due to low balance", result)
mockContext.verify(mockBalanceService)
}
void testProcess_accountIsAboveThreshold() {
// create the mock control and set as collaborator in accountService
def mockContext = new MockFor(BalanceService)
// set expectation and give a mock return value
mockContext.demand.checkBalance(1..1) { -> return 50 }
def mockBalanceService = mockContext.proxyInstance()
accountService.balanceService = mockBalanceService
def result = accountService.process()
assertTrue("process should return true as balance is above threshold", result)
mockContext.verify(mockBalanceService)
}
}
ConclusionGrails mockFor certainly has a defect in it. I also tried this code in grails 1.2.5 and the same issue occurs. I have not tried it in future versions. However, MockFor is a good alternative and provides many other features. Alternatively, you can use Expandos, ExpandoMetaClass and Map Coercion to mock out collaborators. These options are low ceremony and don't provide out of the box validations. The mocking implementation you choose depends on the context of testing and the extent of validations you want to enforce.
Special thanks to Arvind Dhiman for his insite on this issue
Assir
ReplyDeleteIn your demand you defined your closure incorrectally.
You entered:
mockBalanceServiceControl.demand.checkBalance(1..1) { return 24 }
You should have entered
mockBalanceServiceControl.demand.checkBalance(1..1) { -> return 24 }
Since the closure takes zero parameters, you have to define it as { -> ... } not { }
Best Regards,
Paul WOods
Yes, Paul. Thanks you are correct. I have run into this issue before with mocking closures, and it had caused problems in the past. I am correcting the code in the blog. Thanks!
ReplyDeleteIs there any way to get MockFor to work when mocking a service or interface defined in Java? (e.g. with non dynamic return type)
ReplyDeleteYes, you can use this in Java. I haven't used it for testing Java classes, but the documentation says it can. You must use instance style with proxy. See "Instance-style MockFor and StubFor" section, last paragraph here:
ReplyDeletehttp://groovy.codehaus.org/Groovy+Mocks
Reading white on black, ouh la la.. my poor eyes.
ReplyDeleteThanks for your simple example of mockFor Vs. MockFor. For me the real problem with the Grails mockFor is that it is not available for integration testing but you get no error message about that if you accidentally use it (beside the fact that it worked in older Grails versions).
ReplyDeleteWhen I was doing some testing before I think I ran into this too, but I was able to solve it by changing the order of the createMock and demand statements from:
accountService.balanceService = mockBalanceServiceControl.createMock()
mockBalanceServiceControl.demand.checkBalance(1..1) { -> return 24 }
to:
mockBalanceServiceControl.demand.checkBalance(1..1) { -> return 24 }
accountService.balanceService = mockBalanceServiceControl.createMock()
I notice in your MockFor code you actually had this change.
Dan Ketcham
Thanks, i appreciate the feedback.
ReplyDeleteInteresting Article
ReplyDeleteSpring Hibernate Online Training | Hibernate Training in Chennai
Hibernate Online Training | Java Online Training | Java EE Online Training
very informative article.And very well explained about different protocols.keep posting like good content posts.For more details please visit our website.
ReplyDeleteOracle Fusion Cloud HCM Online Training
Thanks for sharing this informative content , Great work
ReplyDeleteLeanpitch provides online training in Advanced Scrum Master during this lockdown period everyone can use it wisely.
Advanced Scrum Master Training Online
Thanks for sharing this informative content , Great work
ReplyDeleteLeanpitch provides online training inScrum Master during this lockdown period everyone can use it wisely.
Advanced Scrum Master Training
Thanks for sharing this amazing instructive content. Yellowstone Coat
ReplyDeleteI’m happy by reading your enjoyable article, Keep uploading more interesting articles like this Mens Collection
ReplyDeleteYour info is really amazing with impressive content..Excellent blog with informative concept. Really I feel happy to see this useful blog, Thanks for sharing such a nice blog..
ReplyDeleteGood Post! Thank you so much for sharing the post, it was so good to read and useful to improve.
Wonderful blog & good post.Its really helpful for me, awaiting for more new post. Keep Blogging!
Excellent Blog! I would like to thank for the efforts you have made in writing this post. I am hoping the same best work from you in the future as well. I wanted to thank you for this websites! Thanks for sharing. Great websites
Thanks for such a great blog here. I was searching for something like this for quite a long time and at last, I’ve found it on your blog. It was definitely interesting for me to read about their market situation nowadays.
I am looking for and I love to post a comment that "The content of your post is awesome" Great work!
Nice...Excellent blog and useful to others
This is a great post. I like this topic.This site has lots of advantage.I found many interesting things from this site. It helps me in many ways.Thanks for posting.
This article is very much helpful and i hope this will be an useful information for the needed one. Keep on updating these kinds of informative things...
Your website is very good and nice information was provided in your site, thanks for sharing.
Thanks for sharing the valuable information here. So i think i got some useful information with this content. Thank you and please keep update like this informative details.
This is the first & best article to make me satisfied by presenting good content. I feel so happy and delighted. Thank you so much for this article.
Nice information, valuable and excellent design, as share good stuff with good ideas and concepts, lots of great information and inspiration, both of which I need, thanks to offer such a helpful information here.
Thank you for giving the great article. It delivered me to understand several things about this concept. Keep posting such surpassing articles so that I gain from your great post.
evs full form
raw agent full form
full form of tbh in instagram
dbs bank full form
https full form
tft full form
pco full form
kra full form in hr
tbh full form in instagram story
epc full form
I really amazed to read this blog post. Thanksgiving Sale
ReplyDeleteit was a wonderful chance to visit this kind of site and I am happy to know. thank you so much for giving us a chance to have this opportunity.. Daft Punk Jacket
ReplyDeleteI am so happy to come across this piece of write up, very much advanced my understanding to the next top level. Great job and continue to do same..Sophia Bush Leather Jacket
ReplyDelete