5

私は次の方法を持っています:

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)を確認しましたが、haveErrorのようなカスタムマッチャーを定義する方法がわかりません。お気に入り

4

1 に答える 1

8

ここに、コンパイルするためのいくつかの変更を加えたコードを示します。

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

マッチャーはEither[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および部分関数を使用)を2番目のソリューション(暗黙的を使用)およびbeLikeマッチャーと組み合わせて、既存のspecs2コードの再利用性を最大限に高めることができます。

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)
}
于 2012-09-30T10:17:13.973 に答える