Mar
27
2008

MockFor(March): Unit Testing Grails Services

Ok. We’ve had a look at Taglibs and Controllers, it’s time to take a looks at Services. Let’s start with a simple one… My NotificationService gets called when people post new comments. But before we send a notification, we check to see whether notifications are turned on for the blog in question. This requires a little navigation of the object graph…

    boolean isEmailNotifyActive(Comment comment) {

        BlogProperty bp = comment.blogEntry.blog.blogProperties?.find { prop ->
            prop.name == "emailNotify"
        }
        return bp ? Boolean.valueOf(bp.value) : false
    }

So the only mockery required is making sure that object graph navigation works. I *have* to pass in a real comment since the arg is strongly typed… and I have to return a BlogProperty from the final find… but other than that, you can mock till it’s 1999…

    void testIsEmailActive() {

        def bp = [name: 'emailNotify', value: 'true']
        Comment.metaClass.getBlogEntry = { -> [ blog: [ blogProperties: [
                bp
        ] ] ] }
        def comment = new Comment()
        assertTrue ns.isEmailNotifyActive(comment)
        bp.value = 'false'
        assertFalse ns.isEmailNotifyActive(comment)

    }

So we can mock our way through all the object graph navigation using nested maps, but just make sure that we return a BlogProperty object at the end. We’ll go for the gold and check for both the positive and affirmative. Test coverage markers now work in IntelliJ Groovy Test cases (since 7.0.3), so let’s do a “Run with Coverage”…

The tree view of coverage in IntelliJ

Ok. Not quite 100%, but we’re underway… What’s the IDE source telling us…

The IDE view of coverage

Well those markers are pretty hard to see… but nothing’s red. Not sure why some things are off-pink :-) … that code certainly *seems* to be being covered.. Need to circle back later and check that out.

Let’s try something a little more meaty…sendMailNotification() invokes the actual MailService which is injected into NotificationService by Grails. It should be trickier to mock…

	   def sendEmailNotification(Comment comment, String address, String baseUri) {
	        log.debug "Sending new notification to ${address} on comment to ${comment.blogEntry.title}"
	        mailService.send(address,
					"""
					// lots of markup stuff here around the comment
	                """,
	                "[Gravl] New comment for ${comment.blogEntry.title} by ${comment.author}")

	    }

Just need to pass in a mock MailService to the NotificationService and we’re done right? Right. But MailService is defined in the NotificationService as:

	MailService mailService

So we’re talking concrete class – no hashmap will mock this bad boy. Given this is MockFor(March)… it’s probably time to take advantage of the MockFor() support in Groovy:

   void testSendEmailNotification() {

        String.metaClass.encodeAsWiki = { -> delegate }

        def bp = [name: 'emailNotify', value: 'true']
        Comment.metaClass.getBlogEntry = { -> [ blog: [ blogProperties: [
                bp
        ] ], toPermalink: {}, title: 'Sample Post' ] }
        def comment = new Comment(body: "sample body", author: "Joe User")
        def mockMail = new MockFor(MailService.class)
        def result = [:]
        mockMail.demand.send(1) { address, body, title ->
            result.address = address
            result.body = body
            result.title = title
        }

        mockMail.use {
            ns.mailService = new MailService()

            ns.sendEmailNotification(comment,"abc@abc.com", "http://localhost/demo/")

        }
        assertEquals "abc@abc.com", result.address
        assertEquals "[Gravl] New comment for Sample Post by Joe User", result.title

    }

The interesting bit here is how we mock out the mail service using MockFor(). My mock mail service just captures the incoming params to the send() call (since it’s not the class under test here – I’m just check that my NotificationService is passing it the right stuff). And we’re in business.

Alrighty that’s enough mocking for one day.. Time to go and look at mocking some seemingly unmockable parts of Gravl. I’ll let you know how I go..

About the Author: Glen Smith

3 Comments + Add Comment

  • Hiya!

    Thanks for the great postings on testing. It seems there are basically nothing like these available as of now (is there?).

    I would like to test a Service where I fetch data via the domain class, e.g. Account.executeQuery(“select Book b where b.pages > :pageCount”, [pageCount:100]). Do you have any idea how this could be made to work? I tried fiddling around metaclasses a bit, but it reported no method matching arguments executeQuery(String, Map) was found. It also seemed IntelliJ dea (8.0M1) couldn’t resolve my service class so I had to run the tests on command line.

    Thumbs up for your efforts and thanks in advance for any advice you might have!

  • Heh, it seems I made some fundamental errors (such as trying to access services in a Unit test compared to an Integration test). I now seem to have got it working, so please ignore the last comment :)

    (It seems asking newbie questions always helps in getting things solved rapidly on ones own!)

  • Nice post.

    Have written a series of post on unit testing grails controllers and mocking in general

    http://gitshah.blogspot.com/2010/04/unit-testing-grails-controller-part-1.html
    http://gitshah.blogspot.com/2010/04/how-to-use-mocks-to-verify-behaviour-in.html

    It would be great if you could give some feedback

Leave a comment

Glen Smith

About Glen

Co-author Grails in Action