Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock domain specific closures in Spock

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.

like image 662
Gregor Petrin Avatar asked May 23 '12 08:05

Gregor Petrin


People also ask

How do you mock a method in Spock?

In Spock, a Mock may behave the same as a Stub. So we can say to mocked objects that, for a given method call, it should return the given data. So generally, this line says: itemProvider. getItems will be called once with ['item-'id'] argument and return given array.

Can you use Mockito with Spock?

We can use Mockito stubbing, not Spock's. It works well! To help you with spock mocks, Spock also has annotations to setup mocks from the Subject Collaborator extension: github.com/marcingrzejszczak/…

Why Spock framework?

Spock is a testing and specification framework for Java and Groovy applications. What makes it stand out from the crowd is its beautiful and highly expressive specification language. Thanks to its JUnit runner, Spock is compatible with most IDEs, build tools, and continuous integration servers.


4 Answers

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
}
like image 64
ganta Avatar answered Nov 10 '22 00:11

ganta


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
}
like image 25
Luis Muñiz Avatar answered Nov 09 '22 23:11

Luis Muñiz


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)

}

like image 27
Tomasz Kalkosiński Avatar answered Nov 10 '22 00:11

Tomasz Kalkosiński


def mailService = Mock(MailService)
mockMailService.metaClass.sendMail = { ... your logic ... }
controller.mailService = mailService
like image 34
moskiteau Avatar answered Nov 10 '22 00:11

moskiteau