2016-04-05 51 views
0

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

但是,当我尝试测试否定的情况下,我得到一个编译错误,“值haveHttpStatus不是org.specs2.matcher.MatchResult [选项[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()或结果()的时候,我得到的编译错误

方法MatchResult的特质申请不能在org.specs2访问。 matcher.MatchResult [选项[scala.concurrent.Future [play.api.mvc.Result]]]

+0

的一边善待你的问题,根据本https://www.playframework.com/documentation /2.4.x/ScalaTestingWithSpecs2你可以使用内置的'status(result)mustEqual OK' –

+0

我们最初使用这种方法。当'回应必须是一些。(状态(_)== NOT_FOUND)'失败,我们会得到一个错误信息,比如'Some([email protected])'是Some,但函数在'scala上返回'false'。 concurrent.impl.Promise $ [email protected]''。我们沿着创建自定义匹配器的路线前进,以便如果Jenkins作业中的测试失败,我们会在控制台输出中看到一条消息,如'Some([email protected])'预期的404但是发现了200个,所以我们可以更快地解决问题。 – bhnat

+1

尝试在ide中导航到“not”的源代码,当你不使用parens时,它与使用它们不同。当你这样做的时候,它是一个简单的匹配器,否定你给的那个,而当你不这样做的时候它更像这个'List(1,3)。必须(不).contain(1)'so'must'是应用'def must(m:=> Matcher [T])= applyMatcher(m)'给出'MatchResult [T]',稍后将其隐式转换为'TraversableBeHaveMatchers [T]',其中包含def包含:ValueCheck [T])= s(outer.contain(check))'。你可以找到所有这些只是通过来源导航。 –

回答

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}") 
    } 
} 
+0

我会尝试用不同的自定义匹配来解决这个问题。现在,我得到“value haveHttpStatus不是org.specs2.matcher.MatchResult [Option [scala.concurrent.Future [play.api.mvc.Result]]]的成员”“当我尝试”时不得有HttpStatus(200) ”。但没有错误W/O'不'。 – bhnat

+0

你有范围内的隐式类NotStatusMatcherMatcher吗?因为这个应该给你'haveHttpStatus'方法。 – Eric

+0

我把这个添加到了play-scala-intro模板中的一个例子 - https://github.com/bhnat/play-scala-intro。在工具/ Matchers.scala中,我添加了特征NotHttpMatcher隐式类NotStatusMatcherMatcher(结果:NotMatcher [Option [Future [Result]]]){defHatpStatus(expected:Int)= MyMatchers.haveHttpStatus(expected).not } }'然后在ApplicationSpec中扩展这个特征。 – bhnat