【问题标题】:Negating a custom matcher in specs2否定 specs2 中的自定义匹配器
【发布时间】:2016-07-25 19:24:57
【问题描述】:

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

但是当我尝试测试一个否定的情况时,我得到一个编译错误,“value haveHttpStatus is not a member of org.specs2.matcher.MatchResult[Option[scala.concurrent.Future[play.api.mvc.Result] ]]]"

    "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) 中看到自定义匹配器被括在括号中。这行得通。把“不”放在最后也有效。

    "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,看起来我需要 haveHttpStatus 来返回 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[Option[scala.concurrent.Future[play.api.mvc.Result]]] 中访问 trait MatchResult 中应用的方法]

【问题讨论】:

  • 暂且不提你的问题,根据这个playframework.com/documentation/2.4.x/ScalaTestingWithSpecs2,你可以使用内置的status(result) mustEqual OK
  • 我们最初使用了这种方法。当response must beSome.which(status(_) == NOT_FOUND) 失败时,我们会收到类似'Some(scala.concurrent.impl.Promise$DefaultPromise@14fb07de)' is Some but the function returns 'false' on 'scala.concurrent.impl.Promise$DefaultPromise@14fb07de' 的错误消息。我们沿着创建自定义匹配器的路线走,这样如果我们的 Jenkins 作业中的测试失败,我们会在控制台输出中收到一条消息,例如 'Some(scala.concurrent.impl.Promise$DefaultPromise@4e37d15b)' expected 404 but found 200,这样我们就可以更快地解决问题。
  • 尝试在你的 ide 中导航到 not 的来源,当你不使用括号时,它与使用它们时是不同的。当你这样做时,它是一个简单的匹配器,它否定你给出的那个,而当你不这样做时,它更像是这样List(1, 3).must(not).contain(1) 所以must 被应用def must(m: =&gt;Matcher[T]) = applyMatcher(m)MatchResult[T],稍后会隐式转换到具有def contain(check: ValueCheck[T]) = s(outer.contain(check)) 方法的TraversableBeHaveMatchers[T]。您只需浏览来源即可找到所有这些内容。
  • 这很有帮助 - 我在上面添加了一些额外的细节。看来我此时唯一的挂断是无法为 MatchResult 调用 apply 函数。
  • 你的答案还可以吗?

标签: scala playframework specs2


【解决方案1】:

如果您想使用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}")
  }
}

【讨论】:

  • 我会尝试使用不同的自定义匹配器来解决这个问题。现在,当我尝试“必须没有HttpStatus(200) ”。但是没有“不”的错误。
  • 你有implicit class NotStatusMatcherMatcher 在范围内吗?因为这个应该给你haveHttpStatus方法。
  • 我将此添加到 play-scala-intro 模板中作为示例 - github.com/bhnat/play-scala-intro。在 tools/Matchers.scala 中,我添加了 trait NotHttpMatcher { implicit class NotStatusMatcherMatcher(result: NotMatcher[Option[Future[Result]]]) { def haveHttpStatus(expected:Int) = MyMatchers.haveHttpStatus(expected).not } },然后在 ApplicationSpec 中扩展了该特征。
  • 感谢您制作这个项目。我用正确的方法修正了我的答案。
【解决方案2】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-09
    • 1970-01-01
    相关资源
    最近更新 更多