My favorites | Sign in
Logo
                
Search
for
Updated Nov 05, 2009 by etorreborre
MatchersGuide  
How to add expectations to your examples

Matchers presentation

There are different kind of matchers which are used to assert that some properties must be verified. Generally they are used like this:

"This example presents a matcher" in {
  // verifyThisProperty is a Matcher
  myObject must verifyThisProperty(parameter)
}

Be/Have matchers

Most of the matchers presented below are used with myObject must matcher. However some matchers are starting with the words 'be' or 'have'. Those matchers also have alternate forms allowing them to be used with 'be' or 'have' as separated words:

 // this is equivalent
 "hello" must beMatching("h.*")
 "hello" must be matching("h.*")

 // this is equivalent
 "hello" must not(beMatching("z.*"))
 "hello" must not be matching("z.*")

 // this is equivalent
 List("hello") must not(haveSize(2))
 List("hello") must not have size(2)

 // articles are also allowed
 Map("hello" -> "world") must have the key("hello")

 // be and have matchers can be combined with logical operators too
 "hello" must be matching("h.*") and not be matching("z.*")

Create your own Matcher

Creating a new Matcher is easy. You extend the Matcher class, using a case class and implement the apply method:

  import org.specs.matcher.Matcher

  "A matcher" can {
    "be created as a case class" in {
      case class matchHello(a: String) extends Matcher[String]() {
        def apply(v: => String) = (v == a, "okMessage", "koMessage")
      }
      "hello" must matchHello("hello")
    }
  }

You must return a tuple containing:

A matcher can also be created through as a val:

  import org.specs.matcher.Matcher

  "A matcher" can {
    "be created as a val" in {
      val beEven = new Matcher[Int] {
        def apply(number: => Int) = {
          val b = number
         (b % 2 == 0, b + " is even", b + " is odd")
        }
      }
      2 must beEven
    }
  }

Or through a method:

  import org.specs.matcher.Matcher

  "A matcher" can {
    "be created as a method" in {
      def divide(a: Int) = new Matcher[Int] {
        def apply(number: => Int) = {
          val b = number
          (a % b == 0, b + " divides " + a, b + " doesn't divide " + a)
        }
      }
      10 must divide(100)
      3 must not(divide(100))
    }
  }

Implementation note: in the above examples, the lazy parameter to the apply method is passed to a local val. This avoids unnecessary evaluations when the parameter is also reused to create the result messages.

Create the negation of a matcher

Simply use the not method on a matcher:

 2 must beEven // beEven is a user-made matcher checking if a number is even
 val beOdd = beEven.not
 3 must beOdd

In that case, the ok message of the first matcher is used as a ko message for the second matcher.

Create a Be/Have matcher

When you create a matcher like beEven, you may want it to be also usable with be as a separated words. In order to do so, you need to add an implicit conversion from org.specs.specification.Result[T] (which is the result value of the expression a must be) and add the expected matcher as a method:

  implicit def toOddEvenMatcherResult(result: Result[T]) = new OddEvenMatcherResult(result)
  class OddEvenMatcherResult(result: Result[T]) {
    def even = result.matchWithMatcher(beEven)
    def odd = result.matchWithMatcher(beEven.not)
  }

Combine matchers

You can combine matchers with logical operators: and, or, xor, verifyAll, verifyAny

  "ab" must (beMatching("a") and beMatching("b"))
  "ab" must (beMatching("a") or beMatching("c"))
  "ab" must (beMatching("a") xor beMatching("c"))
  "ab" must verifyAll(beMatching("a"), beMatching("b"))
  "ab" must verifyAny(beMatching("a"), beMatching("b"))

verifyAll must be ok for all matchers and verifyAny must be ok for at least one matcher.

Limit the applicability of a matcher

In some cases, you may want to specify that a matcher is only applicable depending on some conditions:

 "abc" must beMatching(s).when(s == ".")
 "abc" must beMatching(s).unless(s.isEmpty)

Matchers applicable to any object

Note that the eq operator can be very tricky, so be careful to use it on purpose. See  issue 40  for more details.

  • a must beIn(iterable) is ok if iterable.exists(_ == a)
  • a must notBeIn(iterable) is ok if !iterable.exists(_ == a)

variable arguments can also be used with beOneOf:

  • 1 must beOneOf(1, 2, 3)
  • 4 must notBeOneOf(1, 2, 3)

  • a must beEmpty is ok if a defines a isEmpty method and a.isEmpty
  • a must notBeEmpty is ok if a defines a isEmpty method and !a.isEmpty
  • a must verify(f) is ok if f(a) == true (alias: a mustVerify f or a verifies b)
  • a must beLike { case p => aBooleanFunction } is ok if a matches the pattern p and the function aBooleanFunction returns true:
List(1, 2) must beLike { case x::y::Nil => true }
  • a must beNull is ok if a is null
  • a must notBeNull is ok if a is not null
  • a must beAsNullAs(b) is a shortcut for 2 expectations:
  • a must beNull.when(b == null)
    b must beNull.when(a == null)
  • a must haveClass[c] is ok if a.getClass == c
  • a must notHaveClass[c] is ok if a.getClass != c
  • a must haveSuperClass[c] is ok if c.isAssignableFrom a.getClass
  • a must notHaveSuperClass[c] is ok if !(c.isAssignableFrom a.getClass)
  • c1 must beAssignableFrom[c2] is ok if c1 isAssignableFrom c2
  • c1 must notBeAssignableFrom[c2] is ok if !(c1 isAssignableFrom c2)

You can also introduce a failure with the fail function:

  • fail("not implemented yet")

If you want to verify that an exception is thrown

Or we can check the exception type and message:

Or we can use pattern matching for more specific checks:

Matchers applicable to Strings

Smart differences

The default failure message for string comparisons is using "smart differences", that is, if the two strings to compare are short (less than 30 characters) the strings are shown as they are. If the strings are longer, the "detailed diffs" message described below is enabled with 30 as a triggering size for shortening and 20 as the shortening size.

Detailed differences

You can get more detailed failure messages for String comparisons with the detailedDiffs method:

  object SpecificationWithDetailedFailures extends Specification {
    detailedDiffs() // enable detailed differences
    "abc" must_== "ab"    // 'ab[c]' is not equal to 'ab'
    "abcd" must_== "abd"  // 'ab[c]d' is not equal to 'abd'
    "acd" must_== "abd"   // 'a[c]d' is not equal to 'abd'

    // 'the kitt[en] is pret[ty]' is not equal to 'the [s]kitt[y] is pret[en]'
    "the kitten is pretty" must_== "the skitty is preten"   
  }

Note that the pair of separators used to highlight the differences can be changed with:

  detailedDiffs("()")
  // or detailedDiffs("<<>>") to separate with << and >>

You can also specify that the detailed diffs should only be triggered when the strings are more than 10 characters long:

  detailedDiffs("()", 10)

And finally, it is possible to declare that the strings should be shortened between the differences in order to be able to spot them more rapidly:

  detailedDiffs("()", 10, 5) // 5 is the shorten size

  '...aaaaa[bb]aa...aa[cc]aaaaa...' is not equal to '...aaaaa[xx]aa...aa[yy]aaaaa...'
  "aaaaaaabbaaaaaaaaaaaccaaaaaaaa" must_== "aaaaaaaxxaaaaaaaaaaayyaaaaaaaa"   

Matchers applicable to Iterables

Matchers applicable to Maps

Matchers applicable to numerical values

Matchers applicable to Options

  Some(x) must beSome[String].which(_.startWith("abc"))

Matchers applicable to ScalaCheck properties

Let's say you want to implement a function which returns all the prefixes of a given list:

prefixes(List(1, 2, 3)) // => List(List(1), List(1, 2), List(1, 2, 3))

You can use ScalaCheck generators to generate lists of random size:

  // list is an arbitrary list (with at least one element)
  // prefix is a random prefix of list
  // testData contains all the prefixes of list and a random prefix
  val testData = for (list <- listOf1(elements(1, 2, 3, 4));
                               n <- choose(1, list.size-1);
                               val prefix = list.take(n))
                            yield (prefixes(list), prefix)

Then, mixing the org.specs.ScalaCheck trait to your specification, you can check that the prefix property passes all generated data:

  // the generated data must pass the following property
  testData must pass { t: (List[List[Int]], Seq[Int]) => val (prefixes, prefix) = t
    prefixes must contain(prefix)
  }
  • You can also use a simple boolean function prefixes.exist(_ == prefix) instead of
  • the above matcher. The failure message will be however less precise
  • You can express things the other way around:

function must pass(generated_data) instead of generated_data must pass(function)

  • You can simply use the pass matcher to verify a ScalaCheck property:
  import org.scalacheck.Prop

  // this property will alway be true
  val prop = Prop.forAll((a:Int) => true)
  prop must pass

Note: the property function is deprecated from ScalaCheck 1.5. You should use Prop.forAll instead.

ScalaCheck parameters

data must pass {
...
}(set(minSize -> 10, maxSize -> 20, maxDiscarded -> 30, minTestsOk -> 5))

where:

You can also set the properties and display ScalaCheck messages with display instead of set:

data must pass {
...
}(display(minSize -> 10, maxSize -> 20, maxDiscarded -> 30, minTestsOk -> 5))

One liners

It is possible to specify properties directly with the verifies operator:

object StringSpecification extends Specification("String") with Scalacheck {
   "startsWith" verifies { (a: String, b: String) => (a + b).startsWith(a) }
   "endsWith" verifies { (a: String, b: String) => (a + b).endsWith(b) }
}

This will create examples which names are "startsWith" and "endsWith".

Note that you can still use ScalaCheck parameters to control the test generation:

"startsWith" verifies ((a: String, b: String) => (a + b).startsWith(a)).set(minTestsOk->250)

Expectations number

By default, the statement property must pass will count each evaluation as one expectation. You can deactivate this behavior by declaring dontExpectProperties at the beginning of your specification and get finer control of what's counted as an expectation with isExpectation:

  dontExpectProperties()
  Prop.forAll((b: Boolean) => (b == true).isExpectation)
  // or
  Prop.forAll((b: Boolean) => isExpectation(b == true))

Of course, you can globally re-enable expectations counting with expectProperties for the rest of your specification:

  object spec extends Specification with Scalacheck {
    // no expectations count when evaluating properties
    dontExpectProperties()
    Prop.forAll((b: Boolean) => b == true)

    // one expectation will be counted on each property evaluation
    expectProperties()
    Prop.forAll((b: Boolean) => b == true)
  }

Matchers applicable to XML

val actual = <a><b name="value" name2="value2"></b></a> 

// using 'have' + a pair of attribute/value (it is a varargs parameter)
actual must have \("b", "name"->"value") 

This matcher doesn't check the nodes order, if you want to check the node order, add ordered to the matcher:

<a>
  <b/>
  <c/>
</a> must ==/(<a><b/></c></a>).ordered

Matchers applicable to Paths and Files

Most of the time, you should try to minimize the interactions with the File System. However, sometimes this is precisely what you want to specify! The following matchers will help you specify paths and files properties.

For paths (as strings):

For files:

Note: for the above matchers, when paths are compared, separators are always ignored.

Matchers composition for object graphs

You may want to combine several existing matchers to be able to match an entire object graph. For example, given the following class definitions:

trait ObjectGraph { 
  import scala.collection.mutable 

  case class Foo(val name:String) { 
    var singlebar: Bar = _ 
    val bars = mutable.Set[Bar]() 
  } 
  
  case class Bar(val id: Long)
}

You will define the following matchers for the classes Foo and Bar:

import org.specs.matcher._

trait ObjectGraphMatchers extends ObjectGraph with Matchers {
  case class matchFoo(foo: Foo) extends Matcher[Foo] {
    def apply(other: => Foo) = {
      ((beEqualTo(_:String)) ^^^ ((_:Foo).name) and
       (matchOptionalBar(_)) ^^^ ((_:Foo).singlebar) and
       (matchBar(_)).toSet ^^^ ((_:Foo).bars))(foo)(other)
    }
  }
  case class matchOptionalBar(bar: Bar) extends Matcher[Bar] {
    def apply(other: => Bar) = {
      ((beAsNullAs(_:Bar)) or
      ((matchBar(_))))(bar)(other)
    }
  }
  case class matchBar(bar: Bar) extends Matcher[Bar] {
    def apply(other: => Bar) = {
      ((beEqualTo(_:Long)) ^^^ ((_: Bar).id))(bar)(other)
    }
  }
}

In the code above:

Precise failures

Most of the failure messages display an expected value and an actual value. But an actual value on what precisely?

  // will fail with "List(ticket1, ticket2) doesn't have size 3" for example
  machine.tickets must haveSize(3) // machine is a user-defined object

If you wish to get a more precise failure message on what's being tested you can set an alias with the "aka" method ("also known as"):

  // will fail with "the created tickets 'List(ticket1, ticket2)' doesn't have size 3"
  machine.tickets aka "the created tickets" must haveSize(3)

xUnit assertions

specs provides xUnit-like assertions to allow a smooth transition path from Test-Driven Development to Behavior-Driven Development:

import org.specs.matcher._

object jUnitTest extends Specification with xUnit {
  "provide the 'assertTrue' jUnit assertion" {
    assertTrue(1 == 1 + 1)
  }
}

Comment by gethemant, Jan 21, 2008

That closeTo assertion is wrong perhaps.

Comment by etorreborre, Jan 21, 2008

Thanks, I corrected it.

Comment by aa...@harnly.net, Sep 21, 2008

Hmm, I guess the rest of us can't edit the wiki? Anyway, there should be an entry in the table of contents for the "Matchers applicable to Strings" section. Cheers.

Comment by etorreborre, Oct 20, 2008

Good catch Aaron! Too bad I'm not notified of comments, I just saw yours today.


Sign in to add a comment
Hosted by Google Code