How to mock domain specific closures in Spock
Asked Answered
B

4

5

I'd like to test a Grails controller that is sending out emails using the grails Email plugin. I'm at a loss exactly how to mock the sendMail closure in order for interactions to work. Here's my latest version of the test code:

def 'controller should send a multipart email'() {
    given: 'a mocked mailService'
        controller.mailService = Mock(grails.plugin.mail.MailService)
        controller.mailService.sendMail(*_) >> Mock(org.springframework.mail.MailMessage)
    when:
        controller.sendNow()
    then:
        1* _.multipart(true)
}

The controller code looks something like what you'd expect, e.g.:

def mailService
def sendNow() {
    mailService.sendMail {
        multipart true
        to '[email protected]'
        from '[email protected]'
        subject 'a subject'
        body 'a body'
    }
}

If I run this test, I get 0 invocations of my multipart interaction instead of 1. The second line of the given: block seems suspicious to me, but if I try to mock a Closure instead of org.springframework.mail.MailMessage my test crashes. I should also mention that the controller itself works as expected (it couldn't wait for me to figure out the unit tests first).

Edited

Aha, looking at the code with a fresh mind a few hours later, I can see why the above code does not work; in order for me to catch multipart and other DSL calls, I would have to mock the closure itself, not the sendMail method (and I can't do that since the closure is defined inside the controller itself). What I probably can do is check the arguments to the sendMail method to see everything necessary was passed into it.

Bizarre answered 23/5, 2012 at 8:10 Comment(3)
..and it seems I cannot check the arguments as I would like, at least in case of a closure :(Bizarre
In my opinion, you would have to mock the sendMail method, and inside this method have an stub that implements the different properties and methods that are used in the closure. Lets call it an evaluator. The you would initialize the closure's delegate to the evaluatro, and execute the closure. The evaluator should have the assertions. You see that I'm using more junit concepts here. I don't know how easily you can translate that into spock concepts. I will update my answer to place a little code snippet illustrating thatJewelljewelle
@GregorPetrin I've edited my answer, please check if it is a good solution.Herzog
D
3

You can install the greenMail plugin, and use it in an integration test:

From the greenmail plugin home page:

import com.icegreen.greenmail.util.*

class GreenmailTests extends GroovyTestCase {
    def mailService
    def greenMail    

    void testSendMail() {
        Map mail = [message:'hello world', from:'[email protected]', to:'[email protected]', subject:'subject']        

        mailService.sendMail {
            to mail.to
            from mail.from
            subject mail.subject
            body mail.message
        }        

        assertEquals(1, greenMail.getReceivedMessages().length)        
        def message = greenMail.getReceivedMessages()[0]        
        assertEquals(mail.message, GreenMailUtil.getBody(message))
        assertEquals(mail.from, GreenMailUtil.getAddressList(message.from))
        assertEquals(mail.subject, message.subject)
    }    

    void tearDown() {
        greenMail.deleteAllMessages()
    }
}

I'm not a Spock expert but you should be able to translate this junit test to spock style.

Source: http://grails.org/plugin/greenmail

Udpate, alternative by mocking sendMail

This is an answer to Gregor's update. In my opinion, you would have to mock the sendMail method, and inside this method have an stub that implements the different properties and methods that are used in the closure. Lets call it an evaluator. The you would initialize the closure's delegate to the evaluatro, and execute the closure. The evaluator should have the assertions. You see that I'm using more junit concepts here. I don't know how easily you can translate that into spock concepts. You probably would be able to us the behaviour checking facilities of spock.

class MailVerifier {
    void multiPart(boolean v){
        //...
    }

    void to(String address){
        //...
    }

    boolean isVerified() {
        //check internal state obtained by the appropriate invocation of the methods
    }
}

def sendMail(Closure mailDefintion) {
    def evaluator = createMailVerifier()
    mailDefinition.delegate = evaluator

    mailDefinition()

    assert evaluator.verified
}
Dextrocular answered 23/5, 2012 at 8:31 Comment(7)
Huh, thank you, I didn't know about this plugin, very useful. I'll accept your answer if no pure Spock solution is suggested. I also like this technique where all the parameters are prepared beforehand and then the closure is called - one of my main concerns is that I can't even get the code inside the sendMail closure to evaluate during tests, leaving a potential can of worms open!Bizarre
And just to explain why I would prefer a pure Spock solution: it is more general and would come handy for testing other things as well. Having used RMocks a lot under Rails, everything is easily mockable there and I feel Spock should be able to do similarly well, I'm probably just not seeing something..Bizarre
Accepting the answer as it's a very good suggestion, and I've figured out my 'how to do it with Spock' dilemma as well.Bizarre
@GregorPetrin would you mind to share your own answer? I'm really interested in seeing you solution to test the multipart stuff.Adamski
@Adamski I ended up with this: gist.github.com/gregopet/7aedd77f4cf638fa31a9Bizarre
@GregorPetrin interesting use of delegate, thanks for sharing your solution.Adamski
@GregorPetrin excellent!! I'll take that into my bag of mocking tricks!Jewelljewelle
S
4

I was able to achieve this in Spock with the following:

def messageBuilder
def bodyParams
def setup(){
    def mockMailService = new MockFor(MailService)
    mockMailService.ignore.sendMail{ callable ->
        messageBuilder = new MailMessageBuilder(null, new ConfigObject())
        messageBuilder.metaClass.body = { Map params ->
            bodyParams = params
        }
        callable.delegate = messageBuilder
        callable.resolveStrategy = Closure.DELEGATE_FIRST
        callable.call()
    }
    service.mailService = mockMailService.proxyInstance()
}

And an example test:

def "sendEmailReceipt_passesCorrectParams"(){
    when:
        def receiptItems = [] << [item: "item1", price: 100]
        service.sendEmailReceipt(receiptItems, "[email protected]")

    then:
        messageBuilder.message.to[0] == "[email protected]"
        messageBuilder.message.subject == "My subject"
        bodyParams.view == "/mailtemplates/emailReceipt"
        bodyParams.model.receiptItems == data
}
Saito answered 27/8, 2013 at 4:24 Comment(1)
Nice, this worked for me. Just an addition: When using sendMail { body 'Hello World' } instead of rendering a view, bodyParams will be null. You have to instead check the contents of the mail using messageBuilder.textContent.Incarnadine
D
3

You can install the greenMail plugin, and use it in an integration test:

From the greenmail plugin home page:

import com.icegreen.greenmail.util.*

class GreenmailTests extends GroovyTestCase {
    def mailService
    def greenMail    

    void testSendMail() {
        Map mail = [message:'hello world', from:'[email protected]', to:'[email protected]', subject:'subject']        

        mailService.sendMail {
            to mail.to
            from mail.from
            subject mail.subject
            body mail.message
        }        

        assertEquals(1, greenMail.getReceivedMessages().length)        
        def message = greenMail.getReceivedMessages()[0]        
        assertEquals(mail.message, GreenMailUtil.getBody(message))
        assertEquals(mail.from, GreenMailUtil.getAddressList(message.from))
        assertEquals(mail.subject, message.subject)
    }    

    void tearDown() {
        greenMail.deleteAllMessages()
    }
}

I'm not a Spock expert but you should be able to translate this junit test to spock style.

Source: http://grails.org/plugin/greenmail

Udpate, alternative by mocking sendMail

This is an answer to Gregor's update. In my opinion, you would have to mock the sendMail method, and inside this method have an stub that implements the different properties and methods that are used in the closure. Lets call it an evaluator. The you would initialize the closure's delegate to the evaluatro, and execute the closure. The evaluator should have the assertions. You see that I'm using more junit concepts here. I don't know how easily you can translate that into spock concepts. You probably would be able to us the behaviour checking facilities of spock.

class MailVerifier {
    void multiPart(boolean v){
        //...
    }

    void to(String address){
        //...
    }

    boolean isVerified() {
        //check internal state obtained by the appropriate invocation of the methods
    }
}

def sendMail(Closure mailDefintion) {
    def evaluator = createMailVerifier()
    mailDefinition.delegate = evaluator

    mailDefinition()

    assert evaluator.verified
}
Dextrocular answered 23/5, 2012 at 8:31 Comment(7)
Huh, thank you, I didn't know about this plugin, very useful. I'll accept your answer if no pure Spock solution is suggested. I also like this technique where all the parameters are prepared beforehand and then the closure is called - one of my main concerns is that I can't even get the code inside the sendMail closure to evaluate during tests, leaving a potential can of worms open!Bizarre
And just to explain why I would prefer a pure Spock solution: it is more general and would come handy for testing other things as well. Having used RMocks a lot under Rails, everything is easily mockable there and I feel Spock should be able to do similarly well, I'm probably just not seeing something..Bizarre
Accepting the answer as it's a very good suggestion, and I've figured out my 'how to do it with Spock' dilemma as well.Bizarre
@GregorPetrin would you mind to share your own answer? I'm really interested in seeing you solution to test the multipart stuff.Adamski
@Adamski I ended up with this: gist.github.com/gregopet/7aedd77f4cf638fa31a9Bizarre
@GregorPetrin interesting use of delegate, thanks for sharing your solution.Adamski
@GregorPetrin excellent!! I'll take that into my bag of mocking tricks!Jewelljewelle
B
1

Take a look at plugin tests here: plugin integration test and here: plugin unit test. In my opinion it would be hard for you to mock all MailService dependencies - factory and builder that builds your mail message. I'd end up with testing only if my controller's sendNow is called.

Edit

I've found this answer. According to it you can try:

def 'controller should send a multipart email'() {
    given: 'a mocked mailService'
        def mockMailService = new Object()
        def mockMessageBuilder = Mock(MessageBuilder)
        mockMailService.metaClass.sendMail = { callable ->
            callable.delegate = mockMessageBuilder
            callable.resolveStrategy = Closure.DELEGATE_FIRST
            callable.call()
        }
        controller.mailService = mockMailService
    when:
        controller.sendNow()
    then:
        1* mockMessageBuilder.multipart(true)

}

Besought answered 23/5, 2012 at 9:13 Comment(2)
I agree, I don't want to mock the whole MailService and its dependencies - but just checking that sendMail does get called (and with the right parameters) is beyond me at this point.Bizarre
@GregorPetrin I've edited my answer, please check if it is a good solution.Herzog
S
1
def mailService = Mock(MailService)
mockMailService.metaClass.sendMail = { ... your logic ... }
controller.mailService = mailService
Sorrel answered 24/5, 2012 at 15:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.