or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-specifications.mddsl-components.mdindex.mdintegration-features.mdmatcher-system.mdmutable-specifications.mdreporting.mdtest-execution.md
tile.json

matcher-system.mddocs/

Matcher System

The specs2 matcher system provides a comprehensive set of type-safe matchers for assertions in specifications. Matchers can be composed, negated, and customized to create expressive and readable test assertions.

Core Matcher Types

Matcher[T]

Base trait for all matchers providing composition and transformation methods.

trait Matcher[T] {
  def apply[S <: T](expectable: Expectable[S]): MatchResult[S]
  
  // Composition methods
  def and[S <: T](m: Matcher[S]): Matcher[S]
  def or[S <: T](m: Matcher[S]): Matcher[S]  
  def not: Matcher[T]
  
  // Transformation methods
  def ^^[S](f: S => T): Matcher[S]
  def when(condition: => Boolean): Matcher[T]
  def unless(condition: => Boolean): Matcher[T]
  def iff(condition: => Boolean): Matcher[T]
  
  // Utility methods
  def eventually: Matcher[T]
  def lazily: Matcher[T]
  def orSkip: Matcher[T]
  def orPending: Matcher[T]
}

MatchResult[T]

Result of applying a matcher to a value.

case class MatchResult[T](
  expectable: Expectable[T],
  message: Message,
  negatedMessage: Message
) {
  def isSuccess: Boolean
  def isFailure: Boolean
  def message: String
  def negatedMessage: String
}

Expectable[T]

Wrapper for values being tested with additional metadata.

case class Expectable[T](
  value: T,
  description: String = "",
  showValue: Boolean = true
) {
  def must[R](m: Matcher[R]): MatchResult[T]
  def should[R](m: Matcher[R]): MatchResult[T]
  def aka(alias: String): Expectable[T]
  def updateDescription(f: String => String): Expectable[T]
}

Matcher Collections

Matchers

Main trait aggregating all standard matchers.

trait Matchers extends AnyMatchers 
  with BeHaveMatchers 
  with TraversableMatchers 
  with MapMatchers
  with StringMatchers 
  with NumericMatchers
  with ExceptionMatchers 
  with OptionMatchers 
  with EitherMatchers
  with FutureMatchers 
  with EventuallyMatchers
  with XmlMatchers 
  with JsonMatchers

MustMatchers

"Must" syntax for expectations that return results.

trait MustMatchers extends Matchers {
  implicit def anyToMust[T](t: T): MustExpectable[T]
}

class MustExpectable[T](value: T) {
  def must[R](m: Matcher[R]): MatchResult[T]
  def must(m: Matcher[T]): MatchResult[T]
}

ShouldMatchers

"Should" syntax for expectations.

trait ShouldMatchers extends Matchers {
  implicit def anyToShould[T](t: T): ShouldExpectable[T]
}

class ShouldExpectable[T](value: T) {
  def should[R](m: Matcher[R]): MatchResult[T]
  def should(m: Matcher[T]): MatchResult[T]
}

Core Matcher Categories

AnyMatchers

General-purpose matchers for any type.

trait AnyMatchers {
  def beTrue: Matcher[Boolean]
  def beFalse: Matcher[Boolean]
  def beNull[T]: Matcher[T]
  def beEqualTo[T](t: T): Matcher[T]
  def be_===[T](t: T): Matcher[T]
  def beOneOf[T](values: T*): Matcher[T]
  def beAnInstanceOf[T: ClassTag]: Matcher[Any]
  def haveClass[T: ClassTag]: Matcher[Any]
  def haveSuperclass[T: ClassTag]: Matcher[Any]
  def beAssignableFrom[T: ClassTag]: Matcher[Class[_]]
}

Usage Examples:

true must beTrue
null must beNull
5 must beEqualTo(5)
"test" must beOneOf("test", "spec", "example")
List(1,2,3) must beAnInstanceOf[List[Int]]

StringMatchers

String-specific matchers.

trait StringMatchers {
  def contain(s: String): Matcher[String]
  def startWith(s: String): Matcher[String]
  def endWith(s: String): Matcher[String]
  def beMatching(regex: String): Matcher[String]
  def beMatching(regex: Regex): Matcher[String]
  def find(regex: String): Matcher[String]
  def have size(n: Int): Matcher[String]
  def have length(n: Int): Matcher[String]
  def be empty: Matcher[String]
  def beBlank: Matcher[String]
}

Usage Examples:

"hello world" must contain("world")
"specs2" must startWith("spec")
"testing" must endWith("ing")
"abc123" must beMatching("\\w+\\d+")
"" must be empty
"   " must beBlank

TraversableMatchers

Matchers for collections and traversable types.

trait TraversableMatchers {
  def contain[T](t: T): Matcher[Traversable[T]]
  def containMatch[T](regex: String): Matcher[Traversable[T]]
  def containPattern[T](regex: String): Matcher[Traversable[T]]
  def haveSize[T](n: Int): Matcher[Traversable[T]]
  def haveLength[T](n: Int): Matcher[Traversable[T]]
  def be empty[T]: Matcher[Traversable[T]]
  def beSorted[T: Ordering]: Matcher[Traversable[T]]
  def containAllOf[T](elements: T*): Matcher[Traversable[T]]
  def containAnyOf[T](elements: T*): Matcher[Traversable[T]]
  def atLeastOnce[T](m: Matcher[T]): Matcher[Traversable[T]]
  def atMostOnce[T](m: Matcher[T]): Matcher[Traversable[T]]
  def exactly[T](n: Int, m: Matcher[T]): Matcher[Traversable[T]]
}

Usage Examples:

List(1, 2, 3) must contain(2)
List(1, 2, 3) must haveSize(3)
List.empty[Int] must be empty
List(1, 2, 3) must beSorted
List(1, 2, 3, 2) must containAllOf(1, 2)
List("a", "b", "c") must atLeastOnce(startWith("a"))

NumericMatchers

Numeric comparison matchers.

trait NumericMatchers {
  def beLessThan[T: Ordering](n: T): Matcher[T]
  def beLessThanOrEqualTo[T: Ordering](n: T): Matcher[T]
  def beGreaterThan[T: Ordering](n: T): Matcher[T]
  def beGreaterThanOrEqualTo[T: Ordering](n: T): Matcher[T]
  def beBetween[T: Ordering](min: T, max: T): Matcher[T]
  def beCloseTo[T: Numeric](expected: T, delta: T): Matcher[T]
  def bePositive[T: Numeric]: Matcher[T]
  def beNegative[T: Numeric]: Matcher[T]
  def beZero[T: Numeric]: Matcher[T]
}

Usage Examples:

5 must beLessThan(10)
10 must beGreaterThanOrEqualTo(10)
7 must beBetween(5, 10)
3.14159 must beCloseTo(3.14, 0.01)
5 must bePositive
-3 must beNegative
0 must beZero

ExceptionMatchers

Matchers for testing exceptions.

trait ExceptionMatchers {
  def throwA[E <: Throwable: ClassTag]: Matcher[Any]
  def throwAn[E <: Throwable: ClassTag]: Matcher[Any]
  def throwA[E <: Throwable: ClassTag](message: String): Matcher[Any]
  def throwA[E <: Throwable: ClassTag](messagePattern: Regex): Matcher[Any]
  def throwA[E <: Throwable: ClassTag](matcher: Matcher[E]): Matcher[Any]
}

Usage Examples:

{ throw new IllegalArgumentException("bad arg") } must throwAn[IllegalArgumentException]
{ 1 / 0 } must throwA[ArithmeticException]
{ validate("") } must throwA[ValidationException]("empty input")
{ parse("invalid") } must throwA[ParseException](beMatching(".*invalid.*"))

OptionMatchers

Matchers for Option types.

trait OptionMatchers {
  def beSome[T]: Matcher[Option[T]]
  def beSome[T](t: T): Matcher[Option[T]]
  def beSome[T](matcher: Matcher[T]): Matcher[Option[T]]
  def beNone[T]: Matcher[Option[T]]
}

Usage Examples:

Some(5) must beSome
Some("test") must beSome("test")
Some(10) must beSome(beGreaterThan(5))
None must beNone

EitherMatchers

Matchers for Either types.

trait EitherMatchers {
  def beRight[T]: Matcher[Either[_, T]]
  def beRight[T](t: T): Matcher[Either[_, T]]
  def beRight[T](matcher: Matcher[T]): Matcher[Either[_, T]]
  def beLeft[T]: Matcher[Either[T, _]]
  def beLeft[T](t: T): Matcher[Either[T, _]]
  def beLeft[T](matcher: Matcher[T]): Matcher[Either[T, _]]
}

Usage Examples:

Right(42) must beRight
Right("success") must beRight("success")
Left("error") must beLeft
Left(404) must beLeft(beGreaterThan(400))

FutureMatchers

Matchers for asynchronous Future types.

trait FutureMatchers {
  def await[T]: Matcher[Future[T]]
  def await[T](duration: Duration): Matcher[Future[T]]
  def beEqualTo[T](t: T): FutureMatcher[T]
  def throwA[E <: Throwable: ClassTag]: FutureMatcher[Any]
}

trait FutureMatcher[T] extends Matcher[Future[T]] {
  def await: Matcher[Future[T]]
  def await(duration: Duration): Matcher[Future[T]]
}

Usage Examples:

Future(42) must beEqualTo(42).await
Future.failed(new RuntimeException) must throwA[RuntimeException].await
Future(slow()) must beEqualTo(result).await(5.seconds)

EventuallyMatchers

Retry-based matchers for eventually consistent conditions.

trait EventuallyMatchers {
  def eventually[T](m: Matcher[T]): Matcher[T]
  def eventually[T](m: Matcher[T], retries: Int): Matcher[T]
  def eventually[T](m: Matcher[T], sleep: Duration): Matcher[T]
  def eventually[T](m: Matcher[T], retries: Int, sleep: Duration): Matcher[T]
  def retry[T](m: Matcher[T]): Matcher[T]
  def atMost[T](duration: Duration): RetryMatcher[T]
  def atLeast[T](duration: Duration): RetryMatcher[T]
}

Usage Examples:

asyncOperation() must eventually(beEqualTo(expected))
database.count() must eventually(beGreaterThan(0), retries = 10)
cache.get(key) must eventually(beSome, 100.millis)

MapMatchers

Matchers for Map types.

trait MapMatchers {
  def haveKey[K](k: K): Matcher[Map[K, _]]
  def haveValue[V](v: V): Matcher[Map[_, V]]
  def havePair[K, V](k: K, v: V): Matcher[Map[K, V]]
  def havePairs[K, V](pairs: (K, V)*): Matcher[Map[K, V]]
  def haveKeys[K](keys: K*): Matcher[Map[K, _]]
  def haveValues[V](values: V*): Matcher[Map[_, V]]
}

Usage Examples:

Map("a" -> 1, "b" -> 2) must haveKey("a")
Map("a" -> 1, "b" -> 2) must haveValue(1)
Map("a" -> 1, "b" -> 2) must havePair("a" -> 1)
Map("a" -> 1, "b" -> 2) must haveKeys("a", "b")

Matcher Composition

Logical Composition

Combine matchers with logical operators:

// AND composition
result must (beGreaterThan(0) and beLessThan(100))

// OR composition
result must (beEqualTo("success") or beEqualTo("ok"))

// Negation
result must not(beEmpty)
result must not(contain("error"))

Conditional Matching

Apply matchers conditionally:

// When condition is true
result must beEqualTo(expected).when(enableValidation)

// Unless condition is true  
result must beEmpty.unless(hasData)

// If and only if condition is true
result must bePositive.iff(isEnabled)

Transformation

Transform values before matching:

// Transform with function
users must haveSize(3) ^^ (_.filter(_.active))

// Transform with partial function
response must beEqualTo(200) ^^ { case HttpResponse(code, _) => code }

Custom Matchers

Creating Custom Matchers

def customMatcher[T](f: T => Boolean, description: String): Matcher[T] = {
  (t: T) => {
    val result = f(t)
    MatchResult(result, s"$t $description", s"$t does not $description")
  }
}

Examples:

def beValidEmail = beMatching("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b".r) ^^
  ((_: String).toLowerCase, "a valid email address")

def haveValidChecksum = customMatcher[File](
  file => calculateChecksum(file) == expectedChecksum,
  "have valid checksum"
)

"user@example.com" must beValidEmail
new File("data.txt") must haveValidChecksum

Matcher Combinators

Build complex matchers from simple ones:

def beValidUser = (
  have(name = not(beEmpty)) and
  have(email = beValidEmail) and  
  have(age = beGreaterThan(0))
)

User("john", "john@test.com", 25) must beValidUser

Advanced Features

BeHaveMatchers

Natural language matchers using "be" and "have":

trait BeHaveMatchers {
  def be(m: Matcher[Any]): Matcher[Any]
  def have(m: Matcher[Any]): Matcher[Any]
}

Usage:

user must be(valid)
list must have(size(3))
file must be(readable)
response must have(status(200))

Scope Matchers

Test values within specific scopes:

users must contain { user: User =>
  user.name must startWith("John")
  user.age must beGreaterThan(18)
}

Message Customization

Customize matcher failure messages:

def bePositive = be_>=(0) ^^ ((_: Int), "a positive number")

(-5) must bePositive
// Failure: -5 is not a positive number

Best Practices

  1. Use descriptive matchers: Choose matchers that clearly express intent
  2. Compose logically: Use and/or for complex conditions
  3. Custom matchers for domain logic: Create domain-specific matchers for better readability
  4. Handle async properly: Use eventually and await for asynchronous operations
  5. Meaningful failure messages: Customize messages for better debugging
  6. Type safety: Leverage Scala's type system for compile-time safety
  7. Performance considerations: Be mindful of expensive operations in matcher composition