在specs2

时间:2016-04-05 17:41:08

标签: scala playframework specs2

我在specs2中编写了一个自定义匹配器,如下所示:

object MyMatchers {
  def haveHttpStatus(expected:Int) = new StatusMatcher(expected)
}

class StatusMatcher(expected:Int) extends Matcher[Option[Future[Result]]] {

  def apply[R <: Option[Future[Result]]](r: Expectable[R]) = {
    val v = r.value
    v match {
      case None => failure(s"${r.description} was None", r)
      case Some(fr:Future[Result]) =>
        import play.api.test.Helpers._
        val actual:Int = status(fr)
        result(actual == expected,
            s"${r.description} has status $actual as expected",
            s"${r.description} expected status $expected but found $actual",
            r)
      case _ =>
        failure(s"${r.description} has unexpected type $v", r)
    }
  }
}

当我测试肯定的情况时,它按预期工作:

    "return OK" in new WithApplication {
      val response = route(FakeRequest(HttpVerbs.GET, "/test"))
      import tools.MyMatchers._
      response must haveHttpStatus(OK)
    }

但是当我尝试测试一个否定的情况时,我得到一个编译错误,&#34;值hasHttpStatus不是org.specs2.matcher.MatchResult的成员[Option [scala.concurrent.Future [play.api。 mvc.Result]]]&#34;

    "return OK" in new WithApplication {
      val response = route(FakeRequest(HttpVerbs.GET, "/test"))
      import tools.MyMatchers._
      response must not haveHttpStatus(OK)
    }

我在一个示例(https://gist.github.com/seratch/1414177)中看到,自定义匹配器包含在括号中。这很有效。把'不是'&#39;最后也工作了。

    "return OK" in new WithApplication {
      val response = route(FakeRequest(HttpVerbs.GET, "/test"))
      import tools.MyMatchers._
      response must not (haveHttpStatus(OK))
    }

    "also return OK" in new WithApplication {
      val response = route(FakeRequest(HttpVerbs.GET, "/test"))
      import tools.MyMatchers._
      response must haveHttpStatus(OK) not
    }

但我并不清楚为什么这两种方法有效,但最初的否定尝试并非如此。如果有人能够对此有所了解,我真的很想了解每种方法的不同之处。这是在Play Framework 2.4.6项目中,包括specs2为specs2 % Test

要查看返回的类型,我发现:

"return OK" in new WithApplication {
  val response = route(FakeRequest(HttpVerbs.GET, "/test"))
  import tools.MyMatchers._
  val matcher1 = haveHttpStatus(OK)       // <-- is type StatusMatcher
  val matcher2 = (haveHttpStatus(OK))     // <-- is type StatusMatcher
  val matcher3 = not (haveHttpStatus(OK)) // <-- is type AnyRef with Matcher[Option[Future[Result]]]
  val matcher4 = not haveHttpStatus(OK)   // <-- doesn't compile - gives the error "value haveHttpStatus is not a member of org.specs2.matcher.NotMatcher[Any]"

  response must haveHttpStatus(OK)
}

通过AnyBeHaveMatchers,看起来我需要hasHttpStatus来返回一个MatchResult,而不是一个StatusMatcher,但是我很难从这里到那里。

更新

我通过SizedCheckedMatcher钻取,然后在TraversableBaseMatchers特征中使用

def haveSize[T : Sized](check: ValueCheck[Int]) = new SizedCheckedMatcher[T](check, "size")

然后在TraversableBeHaveMatchers中,有一个HasSize类,当你调用时会返回一个MatchResult

def size(n: Int) : MatchResult[T] = s(outer.haveSize[T](n))

这与https://github.com/etorreborre/specs2/blob/master/tests/src/test/scala/org/specs2/matcher/LogicalMatcherSpec.scala中的CustomMatcher示例非常相似。

我试图复制的问题是,当调用s()或result()时,我得到编译错误

  

方法适用于特质在org.specs2.matcher.MatchResult中无法访问MatchResult [Option [scala.concurrent.Future [play.api.mvc.Result]]]

2 个答案:

答案 0 :(得分:0)

如果您想使用a must not beOk[A]语法,其中beOk是自定义匹配器,则需要提供隐式转换(示例here):

implicit class NotStatusMatcherMatcher(result: NotMatcher[Option[Future[Result]]]) {
  def haveHttpStatus(expected:Int) = 
    result.applyMatcher(MyMatchers.haveHttpStatus(expected))
}

顺便提一下,创建自定义匹配器的一种简单方法是使用隐式转换:

import org.specs2.matcher.MatcherImplicits._

type Res = Option[Future[Result]]

def haveHttpStatus(expectedStatus: Int): Matcher[Res] = { actual: Res =>
  actual match {
    case None => 
      (false, s"the result was None")

    case Some(fr:Future[Result]) =>
      import play.api.test.Helpers._
      val actualStatus = status(fr)
      (actualStatus == expectedStatus,
       s"expected status $expectedStatus but found $actualStatus")

    case v =>
      (false, s"unexpected type ${v.getClass}")
  }
}

答案 1 :(得分:0)

您的示例不起作用的原因是因为您使用了中缀表示法。

最简单的解决方案是将自定义匹配器放在 () 之间:

response must not (haveHttpStatus(OK))

或者放在最后:

response must haveHttpStatus(OK) not

为什么原始代码不起作用? 检查编译器如何解释下一个表达式:

// original code that doesn't work
not haveHttpStatus(OK)
// how compiler translates it
not.haveHttpStatus(OK)

如您所见,编译器试图找到一个方法 haveHttpStatus 作为未实现的 NotMatcher[Any] 的一部分(除非另一个答案提到您将其实现为扩展方法)。

但是,如果您添加 (),编译器会理解其中的完整代码是中缀方法的主体,而不是扩展方法:

// original code that works
not (haveHttpStatus(OK))

// compiler reads
not(haveHttpStatus(OK))