如何编写自定义Spock交互迷你DSL来测试我的邮件发送?

时间:2018-08-15 12:25:46

标签: groovy spock

我无法为简洁明了的Spock测试编写自定义交互。

这是我到目前为止所拥有的:

class FunctionTestMailSpec extends Specification {
  ...


  def "process functional test email"(){

    ...
    stuff that processes the mail and ends up calling sendMailSvc.sendEmail()
    ...

    then: "mail should be sent to the expected address"

    // (A)
    1 * sendMailSvc.sendEmail(testMsg.sesMessageId, _ as Message ) >> {
      msgId, Message msg ->
      assert msg.subject == testMsg.variables.subject
      assert msg.to.flatten() == [mime.parseMailbox(keywordRealAddress)]
    }

    // (B)
    1 * sendMailSvc.sendEmail(testMsg.sesMessageId, _ as Message ) >> {
      msgId, Message msg -> assertMessage(
        msg, testMsg.variables.subject, keywordRealAddress)
    }

    // (C)
    1 * sendMailSvc.sendEmail(testMsg.sesMessageId, _ as Message ) >> {
      String msgId, Message msg ->
      new MessageAssertion().
        withSubject(testMsg.variables.subject).
        withTo(keywordRealAddress).apply(msgId, msg)
    }

    // (D)
    // GroovyCastException: Cannot cast object 'org.codehaus.groovy.runtime.MethodClosure@1e4c4fda' with class 'org.codehaus.groovy.runtime.MethodClosure' to class 'com.amazonaws.services.simpleemail.model.SendRawEmailResult'
    1 * sendMailSvc.sendEmail(testMsg.sesMessageId, _ as Message ) >>
      new MessageAssertion().
        withSubject(testMsg.variables.subject).
        withTo(keywordRealAddress).&apply
  }

  void assertMessage(
    Message message,
    String subject,
    String[] to
  ){
    assert message.subject == subject
    assert message.to.flatten() == to.collect{mime.parseMailbox(it)}
  }
}

class MessageAssertion implements BiFunction<String, Message, SendRawEmailResult> {
  String sesMessageId
  String subject
  Mailbox[] to = []

  MimeUtil mime = new MimeUtil()

  def withSubject(String subject){
    this.subject = subject
    this
  }

  def withSesMessageId(String sesMessageId){
    this.sesMessageId = sesMessageId
    this
  }

  def withTo(String[] to){
    this.to = to.collect{mime.parseMailbox(it)}
    this
  }

  @Override
  SendRawEmailResult apply(String sesMessageId, Message message){
    if( this.sesMessageId ){
      assert sesMessageId == this.sesMessageId
    }
    if( this.subject ){
      assert message.subject == this.subject
    }
    if( this.to ){
      assert message.to.flatten().toArray() == this.to
    }
    return null
  }

}

(A)是最直接的方法,但是有点吵,当我开始想测试发送的消息的更多值时,它会变得更加混乱。

(B)在简洁方面是最好的,但是它的可读性差-很难读取具有位置参数而不是明确列出要测试的每个事物的名称的东西。我想我最终将需要一堆带有各种参数的不同方法,因为我想检查消息的不同部分以进行不同的测试。那将开始变得非常混乱。

就可读性而言,

(C)可能是我能做的最好的事情。在创建一个闭包然后直接调用apply()方法方面感觉有点多余,这就是为什么我尝试执行类似(D)的操作,但由于列出的异常而失败的原因。

理想情况下,我想编写某种Groovy / Spock DSL魔术,以使交互定义看起来像这样:

1 * sendMailSvc.sendEmail(testMsg.sesMessageId, _ as Message ) >>
  new MessageAssertion().
    withSubject(testMsg.variables.subject).
    withTo(keywordRealAddress)

我需要做些什么定义一个DSL,让我可以写这样的交互或与之接近呢?

1 个答案:

答案 0 :(得分:0)

只是一个假设

可能您可以创建一个这样的类:

class A{
    String name_ 
    A make(String name){
        this.name_=name
        return this
    }
    A rightShift(B b){
        b.greet(name_)
        return this
    }
}
//aka SendRawEmailResult
abstract class B{
    abstract def greet(String name)
}
//aka MessageAssertion
class C {
    def asserts=[:]
    Object invokeMethod(String name, Object args){
        if(name.startsWith('with')){
            asserts[name.substring(4)]=args
            return this
        }
        throw new Exception("$name method not supported for ${this.getClass()}")
    }
    Closure apply(){
        return {String x->
            println "assert for `$x`"
            if(asserts.Subject){
                println "assert Subject here"
            }
            asserts.each{k,v->
                println "$k : [${v.getClass()}] $v"
            }
        }
    }
    String toString(){
        return "*** ${this.getClass()}${asserts}"
    }
}

new A().make('world') >> 
    new C().withQwe('qwe123').withZte('zte456').withSubject('subj').apply()