我有以下方法:
def save(entity: A): Either[List[Error],A] + {....
我想用specs2测试
我想在未指定必填字段时测试是否存在特定错误,如下所示:
val noNickname = User( nickname = "",name = "new name",) noNickname.save must beLeft.like { case errors => { atLeastOnceWhen(errors) { case error => { error.errorCode must equalTo(Error.required) error.field must equalTo("nickname") } } } }
它工作正常,但我想定义自己的匹配器,使其更简洁,像这样:
noNickname.save must haveError.like { case error => { error.errorCode must equalTo(Error.required) error.field must equalTo("nickname") } } }
我查看了文档(http://etorreborre.github.com/specs2/guide/org.specs2.guide.Matchers.html#Matchers),但我无法弄清楚如何定义像hasError这样的自定义匹配器.喜欢
解决方法
这里你的代码有一些改动,使它编译:
case class Error(errorCode: String,field: String) def save[A](entity: A): Either[List[Error],A] = Left(List(Error("required","nickname"))) case class User(nickname: String,name: String) val noNickname = User(nickname = "",name = "new name") "save noNickName" >> { save(noNickname) must haveError.like { case error => { error.errorCode must equalTo("required") error.field must equalTo("nickname") } } } def haveError[T] = new ErrorMatcher[T] class ErrorMatcher[T] extends Matcher[Either[List[T],_]] { def apply[S <: Either[List[T],_]](value: Expectable[S]) = result(value.value.left.toOption.isDefined,value.description + " is Left",value.description + " is not Left",value) def like[U](f: PartialFunction[T,MatchResult[U]]) = this and partialMatcher(f) private def partialMatcher[U](f: PartialFunction[T,MatchResult[U]]) = new Matcher[Either[List[T],_]] { def apply[S <: Either[List[T],_]](value: Expectable[S]) = { // get should always work here because it comes after the "and" val errors = value.value.left.toOption.get val res = atLeastOnceWhen[T,U](errors)(f) result(res.isSuccess,value.description+" is Left[T] and "+res.message,value.description+" is Left[T] but "+res.message,value) } } }
请注意,匹配器在[List [T],_]到处定义.
我也想知道在没有找到预期的错误消息的情况下返回的失败消息,当部分函数失败时它们可能不是非常明确.
所以你可能想要使用包含匹配器.像这样:
"save noNickName" >> { save(noNickname) must haveError.containing(Error("required","nickname")) } // I'm reusing the beLeft matcher here def haveError[T]: Matcher[Either[List[T],_]] = beLeft // and using an implicit conversion to extend it implicit def toErrorListMatcher[T](m: Matcher[Either[List[T],_]]): ErrorListMatcher[T] = new ErrorListMatcher[T](m) class ErrorListMatcher[T](m: Matcher[Either[List[T],_]]) { def containing(t: T) = // the 'contain' matcher is adapted to take in an // Either[List[T],_] and work on its left part m and contain(t) ^^ ((e: Either[List[T],_]) => e.left.toOption.get) }
[更新]
第一个解决方案(使用atLeastOnceWhen和部分函数)可以与第二个解决方案(使用隐式)和beLike匹配器组合,以获得现有规范2代码的最大可重用性:
def haveError[T]: Matcher[Either[List[T],_] = beLeft implicit def toErrorListMatcher[T](m: Matcher[Either[List[T],_]]): ErrorListMatcher[T] = new ErrorListMatcher[T](m) class ErrorListMatcher[T](m: Matcher[Either[List[T],_]]) { // beLike checks one element // beLike.atLeastOnce transforms that matcher on a // matcher on a sequence of elements def like[S](f: PartialFunction[T,MatchResult[S]]) = { m and beLike(f).atLeastOnce ^^ ((e: Either[List[T],_]) => e.left.toOption.get) }