我在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]]]
答案 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))