Scalatest自定义匹配器“应包含”

Joo*_*oer 6 scala scalatest

这是我经常遇到的情况,但是我还没有找到解决方案。

假设您有一个人员列表,而您只想验证人员名称。这有效:

persons.map(_.name) should contain theSameElementsAs(List("A","B"))
Run Code Online (Sandbox Code Playgroud)

相反,我宁愿这样写

val toName: Person => String = _.name
persons should contain theSameElementsAs(List("A","B")) (after mapping toName)
Run Code Online (Sandbox Code Playgroud)

因为这就是你怎么说

但是有时候,您想使用一个自定义匹配器,该匹配器不仅可以匹配对象的一个​​属性。怎么可能使用

persons should contain(..)
Run Code Online (Sandbox Code Playgroud)

语法,但能够以某种方式使用自定义匹配器?

在这两种情况下,我都可以使用Hamcrest匹配器轻松地使用JUnit或TestNG解决这两种情况,但是我还没有找到使用ScalaTest做到这一点的方法。

我尝试使用Explicitly特性中的“ before be”语法,但这是不可能的,因为这需要一个“ Normalization”,它定义了“ normalized”方法对参数和返回类型使用相同的类型。因此,不可能将Person更改为String。另外,我还没有成功实现类似trait的“ Explicitly”,因为它不喜欢我返回的Equality [。]类型和/或它不再知道原始列表类型是什么,因此使用“ _.name”可以不编译。

欢迎任何建议。

Ast*_*rid 5

您可以通过单词decided和适度滥用Equality特质来管理类似的事物。这是因为Equality特质的areEqual方法使用泛型类型的参数和类型之一Any,所以你可以用它来比较PersonStringdecided by仅仅需要一个Equality对象,这意味着你不必futz周围Normality

import org.scalactic.Equality
import org.scalatest.{FreeSpec, Matchers}

final class Test extends FreeSpec with Matchers {

  case class Person(name: String)

  val people = List(Person("Alice"), Person("Eve"))

  val namesBeingEqual = MappingEquality[Person, String](p => p.name)

  "test should pass" in {
    (people should contain theSameElementsAs List("Alice", "Eve"))(
      decided by namesBeingEqual)
  }

  "test should fail" in {
    (people should contain theSameElementsAs List("Alice", "Bob"))(
      decided by namesBeingEqual)
  }

  case class MappingEquality[S, T](map: S => T) extends Equality[S] {

    override def areEqual(s: S, b: Any): Boolean = b match {
      case t: T => map(s) == t
      case _    => false
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我不确定我会说这是个好主意,因为它的行为方式并不完全像人们期望的那样Equality,但是它确实有效。

您甚至可以通过隐式转换beingMapped将其添加到其中,从而获得建议的语法after

  implicit class AfterExtensions(aft: TheAfterWord) {
    def beingMapped[S, T](map: S => T): Equality[S] = MappingEquality(map)
    }
  }
Run Code Online (Sandbox Code Playgroud)

我确实尝试after通过Uniformitytrait 使用它,该特性具有涉及的相似方法Any,但是由于规范化是错误的方法,因此遇到了问题:我可以Uniformity[String]根据您的示例创建一个对象,但不能创建一个对象Uniformity[Person]。(原因是,有一种normalized方法返回用于构造Equality对象的泛型类型,这意味着为了将字符串与字符串进行比较,左侧输入必须是字符串。)这意味着编写该字符串的唯一方法是期望值与实际值的顺序与正常情况相反:

"test should succeed" in {
    val mappedToName = MappingUniformity[Person, String](person => person.name)
    (List("Alice", "Eve") should contain theSameElementsAs people)(
      after being mappedToName)
  }

  case class MappingUniformity[S, T](map: S => T) extends Uniformity[T] {

    override def normalizedOrSame(b: Any): Any = b match {
      case s: S => map(s)
      case t: T => t
    }

    override def normalizedCanHandle(b: Any): Boolean =
      b.isInstanceOf[S] || b.isInstanceOf[T]

    override def normalized(s: T): T = s
  }
Run Code Online (Sandbox Code Playgroud)

绝对不是您通常要写的方式。