Analytics

Thursday, December 30, 2010

mockFor and MockFor in Grails

I recently came across a small shortcoming in Grails' mockFor feature. I wasn't able to return a value from a service that was mocked. I get an error where the return value is always a closure, not the value I intended. (Note this occurred in grails v1.1).

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 MockFor

I 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)
    }
}
Conclusion

Grails 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

17 comments:

  1. Assir

    In 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

    ReplyDelete
  2. 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!

    ReplyDelete
  3. Is there any way to get MockFor to work when mocking a service or interface defined in Java? (e.g. with non dynamic return type)

    ReplyDelete
  4. Yes, 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:

    http://groovy.codehaus.org/Groovy+Mocks

    ReplyDelete
  5. Reading white on black, ouh la la.. my poor eyes.

    ReplyDelete
  6. Thanks 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).

    When 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

    ReplyDelete
  7. Thanks, i appreciate the feedback.

    ReplyDelete
  8. very informative article.And very well explained about different protocols.keep posting like good content posts.For more details please visit our website.
    Oracle Fusion Cloud HCM Online Training

    ReplyDelete
  9. Thanks for sharing this informative content , Great work
    Leanpitch provides online training in Advanced Scrum Master during this lockdown period everyone can use it wisely.
    Advanced Scrum Master Training Online

    ReplyDelete
  10. Thanks for sharing this informative content , Great work
    Leanpitch provides online training inScrum Master during this lockdown period everyone can use it wisely.
    Advanced Scrum Master Training

    ReplyDelete
  11. Thanks for sharing this amazing instructive content. Yellowstone Coat

    ReplyDelete
  12. I’m happy by reading your enjoyable article, Keep uploading more interesting articles like this Mens Collection

    ReplyDelete
  13. Your 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..

    Good 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

    ReplyDelete
  14. I really amazed to read this blog post. Thanksgiving Sale

    ReplyDelete
  15. it 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

    ReplyDelete
  16. I 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