Как смоделировать доменные специфические замыкания в Споке

Я хотел бы протестировать контроллер Grails, который отправляет электронные письма с помощью плагина Grails Email. Я в недоумении, как именно издеваться 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 вместо org.springframework.mail.MailMessage мой тест вылетает Я должен также упомянуть, что сам контроллер работает, как и ожидалось (он не мог дождаться, когда я сначала выясню модульные тесты).

отредактированный

Ага, глядя на код свежим взглядом несколько часов спустя, я понимаю, почему вышеприведенный код не работает; для того, чтобы я поймал multipart и другие вызовы DSL, я должен был бы смоделировать само замыкание, а не метод sendMail (и я не могу этого сделать, так как замыкание определено внутри самого контроллера). Что я, вероятно, могу сделать, это проверить аргументы sendMail метод, чтобы увидеть все необходимое был передан в него.

4 ответа

Решение

Вы можете установить плагин 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()
    }
}

Я не эксперт по споку, но вы должны быть в состоянии перевести этот тест джунта на стиль спока.

Источник: http://grails.org/plugin/greenmail

Udpate, альтернатива по издевательству sendMail

Это ответ на обновление Грегора. На мой взгляд, вы должны будете смоделировать метод sendMail, и внутри этого метода есть заглушка, которая реализует различные свойства и методы, используемые в замыкании. Давайте назовем это оценщиком. Вы бы инициализировали делегат замыкания в valuatro и выполнили замыкание. Оценщик должен иметь утверждения. Вы видите, что я использую больше концепций Junit здесь. Я не знаю, как легко вы можете перевести это в понятия спока. Вы, вероятно, могли бы нам использовать средства проверки поведения спока.

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
}

Мне удалось добиться этого в Споке с помощью следующего:

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
}
def mailService = Mock(MailService)
mockMailService.metaClass.sendMail = { ... your logic ... }
controller.mailService = mailService

Взгляните на тесты плагинов здесь: тест интеграции плагинов и здесь: модульный тест плагинов. По моему мнению, вам будет трудно высмеять все зависимости MailService - фабрику и сборщик, который создает ваше почтовое сообщение. Я бы закончил с тестированием, только если мой контроллер называется sendNow.

редактировать

Я нашел этот ответ. В соответствии с этим вы можете попробовать:

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)

}

Другие вопросы по тегам