【问题标题】:How to mock domain specific closures in Spock如何在 Spock 中模拟特定领域的闭包
【发布时间】:2012-05-23 08:10:08
【问题描述】:

我想测试一个使用 grails 电子邮件插件发送电子邮件的 Grails 控制器。我不知道如何模拟 sendMail 闭包以使交互正常工作。这是我最新版本的测试代码:

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

控制器代码看起来像您所期望的那样,例如:

def mailService
def sendNow() {
    mailService.sendMail {
        multipart true
        to 'example@example.org'
        from 'me@here.com'
        subject 'a subject'
        body 'a body'
    }
}

如果我运行这个测试,我会得到 0 次调用我的 multipart 交互而不是 1。given: 块的第二行对我来说似乎很可疑,但如果我尝试模拟 Closure 而不是 @ 987654327@ 我的测试崩溃了。我还应该提到控制器本身按预期工作(它等不及我先弄清楚单元测试)。

已编辑

啊哈,几个小时后重新看代码,我明白为什么上面的代码不起作用了;为了让我捕捉multipart 和其他DSL 调用,我必须模拟闭包本身,而不是sendMail 方法(我不能这样做,因为闭包是在控制器本身内部定义的)。我可能可以做的是检查 argumentssendMail 方法,看看所有必要的东西都传递给它。

【问题讨论】:

  • ..似乎我无法按我的意愿检查参数,至少在关闭的情况下:(
  • 在我看来,您必须模拟 sendMail 方法,并且在该方法内部有一个存根,它实现了闭包中使用的不同属性和方法。让我们称之为评估者。您将初始化闭包的委托给 evaluatro,并执行闭包。评估者应该有断言。你看我在这里使用了更多的junit概念。我不知道你能多么容易地将它转化为 spock 概念。我将更新我的答案以放置一个小代码 sn-p 来说明这一点
  • @GregorPetrin 我已经编辑了我的答案,请检查它是否是一个好的解决方案。

标签: email grails multipart spock


【解决方案1】:

我能够通过以下方式在 Spock 中实现这一点:

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()
}

还有一个示例测试:

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

    then:
        messageBuilder.message.to[0] == "some@email.com"
        messageBuilder.message.subject == "My subject"
        bodyParams.view == "/mailtemplates/emailReceipt"
        bodyParams.model.receiptItems == data
}

【讨论】:

  • 很好,这对我有用。只是一个补充:当使用sendMail { body 'Hello World' } 而不是渲染视图时,bodyParams 将为空。您必须改为使用 messageBuilder.textContent 检查邮件的内容。
【解决方案2】:

您可以安装 greenMail 插件,并在 集成 测试中使用它:

来自greenmail插件主页:

import com.icegreen.greenmail.util.*

class GreenmailTests extends GroovyTestCase {
    def mailService
    def greenMail    

    void testSendMail() {
        Map mail = [message:'hello world', from:'from@piragua.com', to:'to@piragua.com', 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()
    }
}

我不是 Spock 专家,但您应该能够将此 junit 测试翻译成 spock 样式。

来源:http://grails.org/plugin/greenmail

更新,通过模拟 sendMail 替代

这是对 Gregor 更新的回答。在我看来,你必须模拟 sendMail 方法,并且在这个方法内部有一个存根,它实现了闭包中使用的不同属性和方法。让我们称之为评估者。您将初始化闭包的委托给 evaluatro,并执行闭包。评估者应该有断言。你看我在这里使用了更多的junit概念。我不知道你能多么容易地将它转化为 spock 概念。您可能可以使用 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
}

【讨论】:

  • 呵呵,谢谢,我不知道这个插件,很有用。如果没有建议纯 Spock 解决方案,我会接受您的回答。我也喜欢这种技术,它预先准备好所有参数,然后调用闭包——我主要担心的一个问题是,我什至无法在 sendMail 闭包中获取代码以在测试期间进行评估,留下一个潜在的罐头蠕虫打开!
  • 并且只是为了解释为什么我更喜欢纯 Spock 解决方案:它更通用,也可以方便地测试其他东西。在 Rails 下经常使用 RMocks,那里的一切都很容易模拟,我觉得 Spock 应该能够做得很好,我可能只是什么都没看到..
  • 接受答案,因为这是一个非常好的建议,而且我也已经解决了“如何处理 Spock”​​的困境。
  • @GregorPetrin 您愿意分享您自己的答案吗?我真的很想看到你的解决方案来测试多部分的东西。
  • @lucke84 我最终得到了这个:gist.github.com/gregopet/7aedd77f4cf638fa31a9
【解决方案3】:

在此处查看插件测试:plugin integration test 和此处:plugin unit test。在我看来,你很难模拟所有 MailService 依赖项——构建邮件消息的工厂和构建器。只有当我的控制器的 sendNow 被调用时,我才会进行测试。

编辑

我找到了this answer。根据它你可以尝试:

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)

}

【讨论】:

  • 我同意,我不想模拟整个 MailService 及其依赖项 - 但目前仅检查 sendMail 是否被调用(并使用正确的参数)超出了我的范围。
  • @GregorPetrin 我已经编辑了我的答案,请检查它是否是一个好的解决方案。
【解决方案4】:
def mailService = Mock(MailService)
mockMailService.metaClass.sendMail = { ... your logic ... }
controller.mailService = mailService

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-16
    • 1970-01-01
    相关资源
    最近更新 更多