Matchers

Matchers

There are many ways to define expectations in specs2. You can define expectations with anything that returns a Result:

  • Boolean
  • Standard result
  • Matcher result
  • Scalacheck property
  • Mock expectation
  • DataTable
  • Forms

Boolean results

This is the simplest kind of result you can define for an expectation but also the least expressive!

Here's an example:

"This is hopefully true" ! (1 != 2)

This can be useful for simple expectations but a failure will give few information on what went wrong:

"This is hopefully true" ! (2 != 2) // fails with 'the value is false',...

Standard results

Some standard results can be used when you need specific result meanings:

  • success: the example is ok
  • failure: there is a non-met expectation
  • anError: a non-expected exception occurred
  • skipped: the example is skipped possibly at runtime because some conditions are not met. A more specific message can
    be created with Skipped("my message")
  • pending: usually means "not implemented yet", but a specific message can be created with Pending("my message")

Two additional results are also available to track the progress of features:

  • done: a Success with the message "DONE"
  • todo: a Pending with the message "TODO"

Combinators

Logical combinators like and, or, not can be used to combine results. You can also use the eventually method to retry a Result until it is ok (this will actually work with anything convertible to a Result).

Match results

This is by far the largest category of Results in specs2. They cover many data types, can be composed and adapted to create new ones or be created from scratch by the user.

Out of the box

The most common matchers are automatically available when extending the Specification trait:

Specification Matchers

The most common type of matcher is beEqualTo to test for equality. There are different ways to use this matcher:

Matcher Comment
1 must beEqualTo(1) the normal way
1 must be_==(1) with a shorter matcher
1 must_== 1 my favorite!
1 mustEqual 1 if you dislike underscores
1 should_== 1 for should lovers
1 === 1 the ultimate shortcut
1 must be equalTo(1) with a literate style
with a negation
1 must not be equalTo(2)
1 must_!= 2
1 mustNotEqual 2
1 must be_!=(2)
1 !== 2

For some other types of equality:

Matcher Comment
be_=== same as be_== but can be used with some combinators like ^^^ or toSeq because the parameter type is kept
be_==~ checks if (a:A) == (b:A) when there is an implicit conversion from B (the type of b) to A (the type of a)
beTheSameAs checks if a eq b (a must be(b) also works)
beTrue, beFalse shortcuts for Boolean equality
a ==== b similar to a === b but will not typecheck if a and b don't have the same type
a must_=== b similar to a must_== b but will not typecheck if a and b don't have the same type

Note: the beEqualTo matcher is using the regular == Scala equality. However in the case of Arrays, Scala == is just using reference equality, eq. So the beEqualTo matcher has been adapted to use the .deep method on Arrays, transforming them to IndexedSeqs (possibly nested), before checking for equality, so that Array(1, 2, 3) === Array(1, 2, 3) (despite the fact that Array(1, 2, 3) != Array(1, 2, 3)).

These matchers can be used with Any objects:

  • beLike { case exp => ok }: to check if an object is like a given pattern (ok is a predefined value, ko is the opposite)
  • beLike { case exp => exp must beXXX }: to check if an object is like a given pattern, and verifies a condition
  • beNull
  • beAsNullAs: when 2 objects must be null at the same time if one of them is null
  • beOneOf(a, b, c): to check if an object is one of a given list
  • haveClass: to check the class of an object
  • haveSuperclass: to check if the class of an object as another class as one of its ancestors
  • haveInterface: to check if an object is implementing a given interface
  • beAssignableFrom: to check if a class is assignable from another
  • beAnInstanceOf[T]: to check if an object is an instance of type T

There are several matchers to check Option and Either instances:

  • beSome checks if an element is Some(_)
  • beSome(exp) checks if an element is Some(exp)
  • beSome(matcher) checks if an element is Some(a) where a satisfies the matcher
  • beSome(function: A => AsResult[B]) checks if an element is Some(a) where function(a) returns a successful Result
    (note that a Seq[A] is also a function Int => A so if you want to check that a sequence is contained in Some you need to use a matcher: beSome(===(Seq(1)))
  • beSome.which(function) checks if an element is Some(_) and satisfies a function returning a boolean
  • beSome.like(partial function) checks if an element is Some(_) and satisfies a partial function returning a MatchResult
  • beNone checks if an element is None
  • beAsNoneAs checks if 2 values are equal to None at the same time
  • beRight checks if an element is Right(_)

  • beRight(exp) checks if an element is `Right(exp)
  • beRight(matcher) checks if an element is Right(a) where a satisfies the matcher
  • beRight(function: A => AsResult[B]) checks if an element is Right(a) where function(a) returns a successful Result
    (note that a Seq[A] is also a function Int => A so if you want to check that a sequence is contained in Right you need to use a matcher: beRight(===(Seq(1)))
  • beRight.like(partial function) checks if an element is Right(_) and satisfies a partial function returning a MatchResult
  • beLeft checks if an element is Left(_)

  • beLeft(exp) checks if an element is Left(exp)
  • beLeft(matcher) checks if an element is Left(a) where a satisfies the matcher
  • beLeft(function: A => AsResult[B]) checks if an element is Left(a) where function(a) returns a successful Result
    (note that a Seq[A] is also a function Int => A so if you want to check that a sequence is contained in Left you need to use a matcher: beLeft(===(Seq(1)))
  • beLeft.like(partial function) checks if an element is Left(_) and satisfies a partial function returning a MatchResult

There are several matchers to check Try instances:

  • beSuccessfulTry checks if an element is Success(_)
  • beSuccessfulTry.withValue(exp) checks if an element is Success(_)
  • beSuccessfulTry.withValue(matcher) checks if an element is Success(a) where a satisfies the matcher
  • beSuccessfulTry.withValue(function: A => AsResult[B]) checks if an element is Success(a) where function(a) returns a successful Result
    (note that a Seq[A] is also a function Int => A so if you want to check that a sequence is contained in Success you need to use a matcher: beSuccessfulTry.withValue(===(Seq(1)))
  • beSuccessfulTry.which(function) checks if an element is Success(_) and satisfies a function returning a boolean
  • beSuccessfulTry.like(partial function) checks if an element is Success(_) and satisfies a partial function returning a MatchResult
  • beFailedTry checks if an element is Failure(_)
  • beFailedTry.withThrowable[T] checks if an element is Failure(t: T)
  • beFailedTry.withThrowable[T](pattern) checks if an element is Failure(t: T) and t.getMessage matches pattern

Matching on strings is very common. Here are the matchers which can help you:

  • beMatching (or be matching) checks if a string matches a regular expression
  • =~(s) is a shortcut for beMatching("(.|\\s)*"+s+"(.|\\s)*")
  • find(exp).withGroups(a, b, c) checks if some groups are found in a string
  • have length checks the length of a string
  • have size checks the size of a string (seen as an Iterable[Char])
  • be empty checks if a string is empty
  • beEqualTo(b).ignoreCase checks if 2 strings are equal regardless of casing
  • beEqualTo(b).ignoreSpace checks if 2 strings are equal when you replaceAll("\\s", "")
  • beEqualTo(b).trimmed checks if 2 strings are equal when trimmed
  • beEqualTo(b).ignoreSpace.ignoreCase you can compose them
  • contain(b) checks if a string contains another one
  • startWith(b) checks if a string starts with another one
  • endWith(b) checks if a string ends with another one

Less often you need to do comparisons on Numerical values:

  • beLessThanOrEqualTo compares any Ordered type with <=
        1 must be_<=(2)
        1 must beLessThanOrEqualTo(2)

  • beLessThan compares any Ordered type with <
        1 must be_<(2)
        1 must beLessThan(2)

  • beGreaterThanOrEqualTo compares any Ordered type with >=
        2 must be_>=(1)
        2 must beGreaterThanOrEqualTo(1)

  • beGreaterThan compares any Ordered type with >
        2 must be_>(1)
        2 must beGreaterThan(1)

  • beCloseTo checks if 2 Numerics are close to each other
        1.0 must beCloseTo(1, 0.5)
        4 must be ~(5 +/- 2)
        1001.1232455 must beCloseTo(1003.12, 2.significantFigures)
        4.994 must beCloseTo(5.0 within 2.significantFigures)

  • beBetween checks if a value is between 2 others
        5 must beBetween(3, 6)
        5 must beBetween(3, 6).excludingEnd
        5 must beBetween(4, 6).excludingStart
        5 must beBetween(4, 6).excludingBounds
        // with brackets notation
        5 must (be[(4, 7)])

specs2 offers very compact ways of checking that some exceptions are thrown:

  • throwA[ExceptionType] checks if a block of code throws an exception of the given type
  • throwA[ExceptionType](message = "boom") additionally checks if the exception message is as expected
  • throwA(exception) or throwAn(exception) checks if a block of code throws an exception of the same type, with the
      same message
  • throwA[ExceptionType].like { case e => e must matchSomething } or
      throwA(exception).like { case e => e must matchSomething } allow to verify that the thrown exception satisfies a property
  • throwA[ExceptionType](me.like { case e => e must matchSomething } or
      throwA(exception).like { case e => e must matchSomething } allow to verify that the thrown exception satisfies a property

For all the above matchers you can use throwAn instead of throwA if the exception name starts with a vowel for better
readability.

Traversables can be checked with several matchers.

If you want to check the size of a Traversable

  • to check if it is empty
      Seq() must be empty
      Seq(1, 2, 3) must not be empty

  • to check its size
      Seq(1, 2) must have size(2)
      Seq(1, 2) must have length(2) // equivalent to size
      Seq(1, 2) must have size(be_>=(1)) // with a matcher

note: you might have to annotate the haveSize matcher when using some combinators. For example: (futures: Future[Seq[Int]]) must haveSize[Seq[Int]](1).await

  • to check its ordering (works with any type T which has an Ordering)
      Seq(1, 2, 3) must beSorted

Then you can check the elements which are contained in the Traversable

  • if a simple value is contained
      Seq(1, 2, 3) must contain(2)

  • if a value matching a specific matcher is contained
      Seq(1, 2, 3) must contain(be_>=(2))

  • if a value passing a function returning a Result is contained (MatchResult, ScalaCheck Prop,...)
      Seq(1, 2, 3) must contain((i: Int) => i must be_>=(2))

  • note that a Seq[A] is also a function Int => A so if you want to check that a sequence is contained in another you need to use a matcher
      Seq(Seq(1)) must contain(===(Seq(1)))

  • there are also 2 specialized matchers to check the string representation of the elements
      Seq(1234, 6237) must containMatch("23") // matches with ".*23.*"
      Seq(1234, 6234) must containPattern(".*234") // matches with !.*234"

For each of the checks above you can indicate how many times the check should be satisfied:

  • Seq(1, 2, 3) must contain(be_>(0)).forall // this will stop after the first failure
  • Seq(1, 2, 3) must contain(be_>(0)).foreach // this will report all failures
  • Seq(1, 2, 3) must contain(be_>(0)).atLeastOnce
  • Seq(1, 2, 3) must contain(be_>(2)).atMostOnce
  • Seq(1, 2, 3) must contain(be_>(2)).exactly(1.times)
  • Seq(1, 2, 3) must contain(be_>(2)).exactly(1)
  • Seq(1, 2, 3) must contain(be_>(1)).between(1.times, 2.times)
  • Seq(1, 2, 3) must contain(be_>(1)).between(1, 2)

The other types of checks involve comparing the Traversable elements to other elements (values, matchers, function returning a Result)

  • with a set of values
      Seq(1, 2, 3, 4) must contain(2, 4)
      which is the same thing as
      Seq(1, 2, 3, 4) must contain(allOf(2, 4))

  • with a set of matchers
      Seq(1, 2, 3, 4) must contain(allOf(be_>(0), be_>(1)))

  • checking that the order is satisfied
      Seq(1, 2, 3, 4) must contain(allOf(be_>(0), be_>(1)).inOrder)

Note that allOf tries to make each check at least successful once, even if that on the same value. If, on the other hand, you want to specify that each check must succeed on a different value you should use onDistinctValues. For example this will fail:
Seq(1) must contain(allOf(1, 1)).onDistinctValues

The eachOf method does the same thing (and this example will fail as well):
Seq(1) must contain(eachOf(1, 1))

Another frequent use of Traversable matchers is to check if the Traversable have the right number of elements. For this you can use:

  • atLeast, which is actually another name for allOf, where the traversable can contain more elements than required
      Seq(1, 2, 3, 4) must contain(atLeast(2, 4))

  • atMost where the traversable can not contain more elements than required
      Seq(2, 3) must contain(atMost(2, 3, 4))

  • exactly where the traversable must contain exactly the specified number of elements
      Seq(1, 2) must contain(exactly(2, 1))

The atLeast/atMost/exactly operators work on distinct values by default (because this is easier for counting the correspondence between actual values and expected ones). However you can use onDistinctValues(false) if you don't care.

Finally, if you want to get the differences between 2 traversables:

Seq(2, 4, 1) must containTheSameElementsAs(Seq(1, 4, 2))

Maps have their own matchers as well, to check keys and values:

  • haveKey checks if a Map has a given key
        Map(1 -> "1") must haveKey(1)

  • haveKeys checks if a Map has several keys
        Map(1 -> "1", 2 -> "2") must haveKeys(1, 2)

  • haveValue checks if a Map has a given value
        Map(1 -> "1") must haveValue("1")

  • haveValues checks if a Map has several values
        Map(1 -> "1", 2 -> "2") must haveValue("1", "2")

  • havePair checks if a Map has a given pair of values
        Map(1 -> "1") must havePair(1 -> "1")

  • havePairs checks if a Map has some pairs of values
        Map(1->"1", 2->"2", 3->"3") must havePairs(1->"1", 2->"2")

But Maps are also Partial Functions, so:

  • beDefinedAt checks if a PartialFunction is defined for a given value
        partial must beDefinedAt(1)

  • beDefinedBy checks if a PartialFunction is defined for a given value
      and returns another one
        partial must beDefinedBy(1 -> true)

The examples above show how to use matchers:

  • the general form for using a matcher is: a must matcher
  • but can use should instead of must if you prefer
  • for most matchers you can use a form where the be word (or the have word) is detached
  • you can as well negate a matcher by adding not before it (or after it, as a method call)

Optional

These other matchers need to be selectively added to the specification by adding a new trait:

Optional Matchers

That's only if you want to match the result of other matchers!

// you need to extend the ResultMatchers trait
class MatchersSpec extends Specification with matcher.ResultMatchers { def is =
  "beMatching is using a regexp" ! {
    ("Hello" must beMatching("h.*")) must beSuccessful
  }
}

It is very useful to have literal Xml in Scala, it is even more useful to have matchers for it!

  • beEqualToIgnoringSpace compares 2 Nodes, without considering spaces
      <a><b/></a> must ==/(<a> <b/></a>)
      <a><b/></a> must beEqualToIgnoringSpace(<a> <b/></a>)

  • beEqualToIgnoringSpace can also do an ordered comparison
      must ==/( ).ordered

  • on the other hand beEqualToIgnoringSpace will not check attributes order
      must ==/()

  • \ is an XPath-like matcher matching if a node is a direct child of another
      must \("b")

  • You can also check attribute names
      must \("b", "name")

  • And attribute names and values as well (values are checked using a regular expression, use the quote method if you want an exact match)
      must \("b", "n"->"v", "n2"->"v\d")

  • Or the content of a Text node
      hello must \("a") \> "hello" (alias textIs)
      hello must \("a") \>~ "h.*" (alias textMatches)

  • The equivalent of \ for a "deep" match is simply \\
      must \\("c")

Json is a simple data format essentially modeling recursive key-values. There are 2 matchers which can be used to verify the presence of appropriate values in Strings representing Json documents:

  • /(value) checks if a value is present at the root of the document. This can only be the case if that document is an Array

  • /(regex) checks if a value matching the regex is present at the root of the document. This can only be the case if that document is an Array

  • /(key -> value) checks if a pair is present at the root of the document. This can only be the case if that document is a Map

  • */(value) checks if a value is present anywhere in the document, either as an entry in an Array, or as the value for a key in a Map

  • */(key -> value) checks if a pair is present anywhere in a Map of the document

  • /#(i) selects the ith element in a 0-based indexed Array or a Map and allow further checks on that element

Now the interesting part comes from the fact that those matchers can be chained to search specific paths in the Json document. For example, for the following document:

// taken from an example in the Lift project
val person = """{
  "person": {
    "name": "Joe",
    "age": 35,
    "spouse": {
      "person": {
        "name": "Marilyn",
        "age": 33
      }
    }
  }
}
"""

You can use these combinations:

person must /("person") */("person") /("age" -> 33.0) // by default numbers are parsed as Doubles

You can as well use regular expressions or String matchers instead of values to verify the presence of keys or elements. For example:

person must /("p.*".r) */ ".*on".r /("age" -> "\\d+\\.\\d".r)
person must /("p.*".r) */ ".*on".r /("age" -> startWith("3"))
person must /("p.*".r) */ ".*on".r /("age" -> (be_>(30) ^^ ((_:String).toInt)))

You can also access some records by their index:

person must /("person") /# 2 / "person"

Finally you can use Json matchers to match elements in an array:

val json = """{"products":[{"name":"shirt","price":10},{"name":"shoe","price":5}]}"""

def aProductWith(name: Matcher[JsonType],  price: Matcher[JsonType]): Matcher[String] =
  /("name").andHave(name) and /("price").andHave(price)

def haveProducts(products: Matcher[String]*): Matcher[String] =
  /("products").andHave(allOf(products:_*))

json must haveProducts(
  aProductWith(name = "shirt", price = 10),
  aProductWith(name = "shoe", price = 5)
)

The andHave method accepts any Matcher[JsonType] where JsonType is either JsonArray, JsonMap, JsonNumber, JsonString, JsonNull. In the example above we pass directly shirt and 10 as Matcher[JsonType] because there are implicit conversions from Int, Boolean, Double, String and Traversable[String] matchers (like allOf) to a Matcher[JsonType].

The Java api for files is more or less mimicked as matchers which can operate on strings denoting paths or on Files (with the org.specs2.matcher.FileMatchers trait)

  • beEqualToIgnoringSep checks if 2 paths are the same regardless of their separators
      "c:\temp\hello" must beEqualToIgnoringSep("c:/temp/hello")
  • beAnExistingPath checks if a path exists
  • beAReadablePath checks if a path is readable
  • beAWritablePath checks if a path is writable
  • beAnAbsolutePath checks if a path is absolute
  • beAHiddenPath checks if a path is hidden
  • beAFilePath checks if a path is a file
  • beADirectoryPath checks if a path is a directory
  • havePathName checks if a path has a given name
  • haveAsAbsolutePath checks if a path has a given absolute path
  • haveAsCanonicalPath checks if a path has a given canonical path
  • haveParentPath checks if a path has a given parent path
  • listPaths checks if a path has a given list of children
  • exist checks if a file exists
  • beReadable checks if a file is readable

  • beWritable checks if a file is writable
  • beAbsolute checks if a file is absolute
  • beHidden checks if a file is hidden
  • beAFile checks if a file is a file
  • beADirectory checks if a file is a directory
  • haveName checks if a file has a given name
  • haveAbsolutePath checks if a file has a given absolute path
  • haveCanonicalPath checks if afile has a given canonical path
  • haveParent checks if a file has a given parent path
  • haveList checks if a file has a given list of children
File contents

The matchers from the org.specs2.matcher.ContentMatchers trait can help us check the contents of files. For example we can check that 2 text files have the same lines:

(file1, file2) must haveSameLines
file1 must haveSameLinesAs(file2)

We can check that the content of one file is contained in another one:

file1 must containLines(file2)

If the files are binary files we can also check that they have the same MD5 hash:

(file1, file2) must haveSameMD5
 file1 must haveSameMD5As(file2)

Order

It is possible to relax the constraint by requiring the equality or containment to be true regardless of the order of lines:

(file1, file2) must haveSameLines.unordered
 file1 must haveSameLinesAs(file2).unordered
 file1 must containLines(file2).unordered

Missing only

By default, (file1, file2) must haveSameLines will report misplaced lines if any, that is, lines of f1 which appear in f2 but not at the right position. However if file2 is big, this search might degrade the performances. In that case you can turn it off with missingOnly:

(file1, file2) must haveSameLines.missingOnly

Show less differences

If there are too many differences, you can specify that you only want the first 10:

(file1, file2) must haveSameLines.showOnly(10.differences).unordered

In the code above 10.differences builds a DifferenceFilter which is merely a filtering function: (lines1: Seq[String], lines2: Seq[String]) => (Seq[String], Seq[String]). The parameter lines1 is the sequence of lines not found in the second content while lines2 is the sequence of lines not found in the first content.

Directories contents

We can compare the contents of 2 directories. We can for example check if no files are missing and none has been added:

actualDir must haveSamePathsAs(expectedDir)
// with a file filter applied to both the actual and expected directories
actualDir must haveSamePathsAs(expectedDir).withFilter((file: File) => !file.isHidden)

Once we know that all files are present we can check their content:

// the default comparison expects that files are text files and that comparison must be done line by line
actualDir must haveSameFilesAs(expectedDir)

// with a file filter applied to both the actual and expected directories
actualDir must haveSameFilesAs(expectedDir).withFilter((file: File) => !file.isHidden)

// with a MD5 matcher for binary files
actualDir must haveSameFilesAs(expectedDir).withMatcher(haveSameMD5)

// it is also possible to only check the content of actual files when they exist in the expected directory
actualDir must haveSameFilesContentAs(expectedDir)
Lines contents

Files are not the only possible source of lines and it is useful to be able to check the content of a File with a Seq[String]:

file1 must haveSameLinesAs(Seq(line1, line2, line3))

This is because those 2 types implement the org.specs2.text.LinesContent trait, defining:

  • a name for the overall content
  • a method for returning the lines
  • a default method for computing the differences of 2 sequences of lines (in case you need to override this logic)

So if you have a specific type T which you can represent as a Seq[String], you can create an implicit LinesContent and then you'll be able to use the ContentMatchers:

implicit def linesforMyType[T]: LinesContent[T] = new LinesContent[T] {
  def name(t: T) = "My list of lines"
  def lines(t: T): Seq[String] = Seq()// your implementation goes here
}

In the rare case where you want to use the Scala interpreter and execute a script:

class ScalaInterpreterMatchersSpec extends mutable.Specification with ScalaInterpreterMatchers {
  def interpret(s: String): String = "" // you have to provide your own Scala interpreter here

  "A script" can {
    "be interpreted" in {
      "1 + 1" >| "2"
    }
  }
}

Scala provides a parsing library using parser combinators.

You can specify your own parsers by:

  • extending the ParserMatchers trait
  • associating the val parsers variable with your parsers definition
  • using the beASuccess, beAFailure, succeedOn, failOn, errorOn matchers to specify the results of parsing input
      strings. beAPartialSuccess, be aPartialSuccess, succeedOn.partially will allow a successful match only on part of the input
  • using haveSuccessResult and haveFailureMsg to specify what happens only on success or failure. Those matchers accept
      a String or a matcher so that
      . haveSuccessResult("r") <==> haveSuccessResult(beMatching(".*r.*") ^^ ((_:Any).toString)
      . haveFailingMsg("m") <==> haveFailingMsg(beMatching(".*r.*"))

For example, specifying a Parser for numbers could look like this:

import util.parsing.combinator.RegexParsers
import NumberParsers.{number, error}

class ParserSpec extends Specification with matcher.ParserMatchers {  def is = s2"""
  Parsers for numbers

    beASuccess and succeedOn check if the parse succeeds
    ${ number("1") must beASuccess }
    ${ number("1i") must beAPartialSuccess }
    ${ number must succeedOn("12") }
    ${ number must succeedOn("12ab").partially }
    ${ number must succeedOn("12").withResult(12) }
    ${ number must succeedOn("12").withResult(equalTo(12)) }
    ${ number("1") must haveSuccessResult("1") }

    beAFailure and failOn check if the parse fails
    ${ number must failOn("abc") }
    ${ number must failOn("abc").withMsg("string matching regex.*expected") }
    ${ number must failOn("abc").withMsg(matching(".*string matching regex.*expected.*")) }
    ${ number("i") must beAFailure }
    ${ number("i") must haveFailureMsg("i' found") }

    beAnError and errorOn check if the parser errors out completely
    ${ error must errorOn("") }
    ${ error("") must beAnError }
                                                                                """

  val parsers = NumberParsers
}

object NumberParsers extends RegexParsers {
  /** parse a number with any number of digits */
  val number: Parser[Int] = "\\d+".r ^^ {_.toInt}
  /** this parser returns an error */
  val error: Parser[String] = err("Error")
}

Sometimes you just want to specify that a block of code is going to terminate. The org.specs2.matcher.TerminationMatchers trait is here to help. If you mix in that trait, you can write:

Thread.sleep(100) must terminate

// the default is retries=0, sleep=100.millis
Thread.sleep(100) must terminate(retries=1, sleep=60.millis)

Note that the behaviour of this matcher is a bit different from the eventually operator. In this case, we let the current Thread sleep during the given sleep time and then we check if the computation is finished, then, we retry for the given number of retries.

In a further scenario, we might want to check that triggering another action is able to unblock the first one:

action1 must terminate.when(action2)
action1 must terminate.when("starting the second action", action2)
action1 must terminate(retries=3, sleep=100.millis).when(action2)

When a second action is specified like that, action1 will be started and action2 will be started on the first retry. Otherwise, if you want to specify that action1 can only terminate when action2 is started, you write:

action1 must terminate.onlyWhen(action2)

It is highly desirable to have acyclic dependencies between the packages of a project. This often leads to describing the packages structure as "layered": each package on a layer can only depend on a package on a lower layer. specs2 helps you enforce this design property with specific matchers.

Layers definition

First you need to define the packages and their expected dependencies. Mix-in the org.specs2.specification.Analysis trait and define, (taking specs2 as an example):

layers (
  "runner",
  "reporter",
  "specification mutable",
  "mock      form",
  "matcher",
  "execute",
  "reflect    xml  time html",
  "collection control io text main data").withPrefix("org.specs2")

The above expression defines layers as an ordered list of Strings containing space-separated package names. It is supplemented by a withPrefix declaration to factor out the common package prefix between all these packages.

By default, the packages are supposed to correspond to directories in the src/target/scala-<version>/classes directory. If your project has a different layout you can declare another target directory:

layers("...").inTargetDir("out/classes")

Inclusion/Exclusion

Every rule has exceptions :-). In some rare cases, it might be desirable to exclude a class from being checked on a given layer. To do this, you can use the include/exclude methods on the Layer class:

layers (
  "runner",
  "reporter",
  "specification mutable".exclude("mutable.SpecificationWithJUnit"),
  "mock      form",
  "matcher",
  "execute",
  "reflect  xml  time html",
  "collection control io text main data").withPrefix("org.specs2")

The include/exclude methods accept a list of regular expressions to:

  • exclude fully qualified class names (generally, only exclude will be necessary)
  • re-include fully qualified class names if the exclusion list is to big

Verification

Now you've defined layers, you can use the beRespected matcher to check if all the dependencies are verified:

val design = layers("...")
design must beRespected

If some dependencies are not respected:

those dependencies are not satisfied:
org.specs2.main x-> org.specs2.io because org.specs2.io.FileSystem -> org.specs2.main.Arguments
org.specs2.main x-> org.specs2.io because org.specs2.io.FileSystem -> org.specs2.main.ArgumentsArgs

Layers as an Example

The org.specs2.specification.Analysis trait allows to directly embed the layers definition in a Specification and turn it into an Example:

class DependenciesSpec extends Specification with specification.Analysis { def is =
  "this is the application design" ^
    layers(
      "gui commandline",
      "controller",
      "backend"
    )
}

Alternative implementation

Another implementation of the same functionality is available through the org.specs2.analysis.CompilerDependencyFinder trait. This implementation uses the compiler dependency analysis functionality but needs more time, since it recompiles the sources.

The source files are taken from the src/main/scala directory by default but you can change this value by using the Layers.inSourceDir method.

While this implementation is slower than the Classycle one, it might retrieve more dependencies, for example when constants are inlined in class files.

Note: since this functionality relies on the scala compiler library, so you need to add it to your build file:

// use sbt's scalaVersion Setting to define the scala-compiler library version
libraryDependencies <<= scalaVersion { scala_version => Seq(
  "org.specs2" %% "specs2" % 2.4.17 % "test",
  "org.scala-lang" % "scala-compiler" % scala_version % "test")
}

Custom

There are many ways to create matchers for your specific usage. The simplest way is to reuse the existing ones:

  • using logical operators
def beBetween(i: Int, j: Int) = be_>=(i) and be_<=(j)
  • using zip operators for to match tuples
type T = (String, String, String, Seq[(String, Double)])

val t1: T = ("a", "b", "c", Seq(("d", 1.01), ("e", 2.02)))
val t2: T = ("a", "b", "c", Seq(("d", 1.00), ("e", 2.00)))

// create a matcher by zipping matchers to the expected value
def beMatching(expected: T) = expected.zip(startWith, ===, ===, matchSequence)
// match the elements of a sequence with a zipped matcher using string equality for the first field and
// approximate Double equality for the second field
def matchSequence(expected: =>Seq[(String, Double)]) = expected.contain(_.zip(===, ==~)).inOrder

/** type inference doesn't work if this matcher, specialised to Double, is not defined */
def ==~(d: =>Double) = beCloseTo(d +/- 0.1)

t1 must beMatching(t2)
  • adapting the actual value
// This matcher adapts the existing `be_<=` matcher to a matcher applicable to `Any`
def beShort1 = be_<=(5) ^^ { (t: Any) => t.toString.size }
def beShort2 = be_<=(5) ^^ { (t: Any) => t.toString.size aka "the string size" }

// !!! use a BeTypedEqualTo matcher when using aka and equality !!!
def beFive = be_===(5) ^^ { (t: Any) => t.toString.size aka "the string size" }

// The adaptation can also be done the other way around when it's more readable
def haveExtension(extension: =>String) = ((_:File).getPath) ^^ endWith(extension)
  • adapting the actual and expected values. This matcher compares 2 Human objects but set their wealth field to 0
        so that the equals method will not fail on that field:
def beMostlyEqualTo = (be_==(_:Human)) ^^^ ((_:Human).copy(wealth = 0))
// then
Human(age = 20, wealth=1000) must beMostlyEqualTo(Human(age = 20, wealth=1)) toResult // success
  • using eventually to try a match a number of times until it succeeds:
val iterator = List(1, 2, 3).iterator
iterator.next must be_==(3).eventually
// Use eventually(retries, n.millis) to use another number of tries and waiting time
  • using await to create a matcher that will match on Matcher[Future[T]]:
future(1) must be_>(0).await
future { Thread.sleep(100); 1 } must be_>(0).await(retries = 2, timeout = 100.millis)
  • using await to create a Result on a Future that returns a Matcher[T]:
future(1 === 1).await
future(1 === 1).await(retries = 2, timeout = 100.millis)
  • using when or unless to apply a matcher only if a condition is satisfied:
1 must be_==(2).when(false)                        // will return a success
1 must be_==(2).unless(true)                       // same thing

1 must be_==(2).when(false, "don't check this")    // will return a success
1 must be_==(2).unless(true, "don't check this")   // same thing
  • using iff to say that a matcher must succeed if and only if a condition is satisfied:
1 must be_==(1).iff(true)                        // will return a success
1 must be_==(2).iff(true)                        // will return a failure
1 must be_==(2).iff(false)                       // will return a success
1 must be_==(1).iff(false)                       // will return a failure
  • using orSkip to return a Skipped result instead of a Failure if the condition is not met
1 must be_==(2).orSkip
1 must be_==(2).orSkip("Precondition failed")    // prints "Precondition failed: '1' is not equal to '2'"
1 must be_==(2).orSkip((ko:String) => "BAD "+ko) // prints "BAD '1' is not equal to '2'"
  • using orPending to return a Pending result instead of a Failure if the condition is not met
1 must be_==(2).orPending
1 must be_==(2).orPending("Precondition failed")    // prints "Precondition failed: '1' is not equal to '2'"
1 must be_==(2).orPending((ko:String) => "BAD "+ko) // prints "BAD '1' is not equal to '2'"
  • using mute to change a Matcher so that it returns MatchResults with no messages. This is used in Forms to create
        properties showing no messages when they fail

  • using updateMessage(f: String => String) or setMessage(m: String) to change the failure message

  • using <==> or ==> to provide a meaning for the expectation when the failure message would not be clear

// when failing, this displays:
// The byname function has not become a strict one because 'evaluated' is not equal to 'not evaluated'
"The byname function has become a strict one" <==> (parameter === "evaluated")

Note that the sentence describing the expectation is negated when there is a failure. This functionality is provided by the org.specs2.text.Sentences::negateSentence trait. You can override this method if you want/need to provide a better behavior for this feature.

From functions

Another easy way to create matchers, is to use some implicit conversions from functions to Matchers:

val m: Matcher[String]  = ((_: String).startsWith("hello"), "doesn't start with hello")
val m1: Matcher[String] = ((_: String).startsWith("hello"), "starts with hello", "doesn't start with hello")
val m2: Matcher[String] = ((_: String).startsWith("hello"), (s:String) => s+ " doesn't start with hello")
val m3: Matcher[String] = ((_: String).startsWith("hello"), (s:String) => s+ " starts with hello", (s:String) => s+ " doesn't start with hello")
val m4: Matcher[String] = (s: String) => (s.startsWith("hello"), s+" doesn't start with hello")
val m5: Matcher[String] = (s: String) => (s.startsWith("hello"), s+ "starts with hello", s+ " doesn't start with hello")

And if you want absolute power over matching, you can define your own matcher:

class MyOwn extends Matcher[String] {
  def apply[S <: String](s: Expectable[S]) = {
    result(s.value.isEmpty,
           s.description + " is empty",
           s.description + " is not empty",
           s)
  }
}

In the code above you have to:

  • define the apply method (and its somewhat complex signature)

  • use the protected result method to return: a Boolean condition, a message when the match is ok, a message when the
        match is not ok, the "expectable" value. Note that if you change the expectable value you need to use the map method
        on the s expectable (s.map(other)). This way you preserve the ability of the Expectable to throw an Exception if
        a subsequent match fails

  • you can use the description method on the Expectable class to return the full description of the expectable including
        the optional description you setup using the aka method

From Hamcrest

If you have Hamcrest matchers in your project and you want to reuse them as specs2 matchers, you can mix-in the org.specs2.matcher.Hamcrest trait:

class HamcrestSpec extends Specification with Grouped with Hamcrest { def is = s2"""

  Hamcrest matchers can be used as specs2 matchers by mixing in the Hamcrest trait
  for example a beEven Hamcrest matcher can be used in a 'must' expression              ${g1.e1}
    the failure message must contain the matched value and the Hamcrest failure message ${g1.e2}
                                                                                        """

  new g1 {
    e1 := 2 must beEven
    e2 := (3 must beEven).message === "<3> is odd"
  }

  // a Hamcrest matcher for even numbers
  object beEven extends BaseMatcher[Int] {
    def matches(item: Object): Boolean       = item.toString.toInt % 2 == 0
    def describeTo(description: Description) { description.appendText(" is odd") }
  }

}

With sequences

If you have the same "MatchResult" expression that you'd like to verify for different values you can write one of the following:

// stop after the first failure
((_:Int) must be_>(2)).forall(Seq(3, 4, 5))
forall(Seq(3, 4, 5)) ((_:Int) must be_>(2))
// check only the elements defined for the partial function
forallWhen(Seq(3, 10, 15)) { case a if a > 3 => a must be_>(5) }

// try to match all values and collect the results
((_:Int) must be_>(2)).foreach(Seq(3, 4, 5))
foreach(Seq(3, 4, 5)) ((_:Int) must be_>(2))
// check only the elements defined for the partial function
foreachWhen(Seq(3, 10, 15)) { case a if a > 3 => a must be_>(5) }

// succeeds after the first success
((_:Int) must be_>(2)).atLeastOnce(Seq(3, 4, 5))
atLeastOnce(Seq(3, 4, 5)) ((_:Int) must be_>(2))
// check only the elements defined for the partial function
atLeastOnceWhen(Seq(3, 4, 10)) { case a if a > 3 => a must be_>(5) }

ScalaCheck

A clever way of creating expectations in specs2 is to use the ScalaCheck library.

To declare ScalaCheck properties you first need to extend the ScalaCheck trait. Then you can pass functions returning any kind of Result (Boolean, Result, MatchResult) to the prop method and use the resulting Prop as your example body:

"addition and multiplication are related" ! prop { (a: Int) => a + a == 2 * a }

The function that is checked can either return:

// a Boolean
"addition and multiplication are related" ! prop { (a: Int) => a + a == 2 * a }

// a MatchResult
"addition and multiplication are related" ! prop { (a: Int) => a + a must_== 2 * a }

// a Prop
"addition and multiplication are related" ! prop { (a: Int) => (a > 0) ==> (a + a must_== 2 * a) }

Note that if you pass functions using MatchResults you will get better failure messages so you are encouraged to do so.

By default the properties created with prop will be shrinking counter-examples. If you want to avoid this, you can use propNoShrink instead.

Arbitrary instances

By default ScalaCheck uses Arbitrary instances taken from the surrounding example scope. However you'll certainly need to generate your own data from time to time. In that case you can create an Arbitrary instance and make sure it is in the scope of the function you're testing:

// this arbitrary will be used for all the examples
implicit def a = Arbitrary { for { a <- Gen.oneOf("a", "b"); b <- Gen.oneOf("a", "b") } yield a+b }

"a simple property" ! ex1

def ex1 = check((s: String) => s must contain("a") or contain("b"))

You can also be very specific if you want to use an Arbitrary instance only on one example. In that case, just replace the check method with the name of your Arbitrary instance:

"a simple property"       ! ex1
"a more complex property" ! ex2

implicit def abStrings = Arbitrary { for { a <- Gen.oneOf("a", "b"); b <- Gen.oneOf("a", "b") } yield a+b }
def ex1 = abStrings((s: String) => s must contain("a") or contain("b"))

// use a tuple if there are several parameters to your function
def ex2 = (abStrings, abStrings)((s1: String, s2: String) => (s1+s2) must contain("a") or contain("b"))

With Generators

ScalaCheck also allows to create Props directly with the Prop.forAll method accepting Gen instances:

"a simple property"       ! ex1
"a more complex property" ! ex2

def abStrings = for { a <- Gen.oneOf("a", "b"); b <- Gen.oneOf("a", "b") } yield a+b

def ex1 = Prop.forAll(abStrings) { (s: String) => s must contain("a") or contain("b") }
def ex2 = Prop.forAll(abStrings, abStrings) { (s1: String, s2: String) => (s1+s2) must contain("a") or contain("b") }

Test properties

ScalaCheck test generation can be tuned with a few properties. If you want to change the default settings, you have to use implicit values:

implicit val params = Parameters(minTestsOk = 20) // add "verbose = true" to get additional console printing

It is also possible to specifically set the execution parameters on a given property:

"this is a specific property" ! prop { (a: Int, b: Int) =>
  (a + b) must_== (b + a)
}.set(minTestsOk = 200, workers = 3) // use "display" instead of "set" for additional console printing

The parameters you can modify are:

  • minTestsOk: minimum of tests which must be ok before the property is ok (default = 100)
  • maxDiscardRatio: if the data generation discards too many values, then the property can't be proven (default = 5)
  • minSize: minimum size for the "sized" data generators, like list generators (default = 0)
  • maxSize: maximum size for the "sized" data generators (default = 100)
  • workers: number of threads checking the property (default = 1)
  • rng: the random number generator (default = new java.util.Random)
  • callback: a ScalaCheck TestCallback (see the ScalaCheck documentation)
  • loader: a custom classloader (see the ScalaCheck documentation)

You can also set the random generator that is used in all the ScalaCheck generators:

case class MyRandomGenerator() extends java.util.Random {
  // implement a deterministic generator for example
}

"this is a specific property" ! prop { (a: Int, b: Int) =>
  (a + b) must_== (b + a)
}.set(rng = MyRandomGenerator(), minTestsOk = 200, workers = 3)

Expectations

By default, a successful example using a Prop will be reported as 1 success and 100 (or minTestsOk) expectations. If you don't want the number of expectations to appear in the specification statistics just mix-in your specification the org.specs2.matcher.OneExpectationPerProp trait.

Collect values

You can turn on value collection (and display) for all properties by simply adding an implicit

implicit val parameters = collectValues

It is also possible to tweak the display of collected values with collectValuesAnd where you pass a function changing the values to be displayed

// drop the first value, it is too verbose
  implicit val parameters = collectValuesAnd((values: Set[Any]) => values.drop(1))

Mock expectations

At the moment only the Mockito library is supported.

Mockito allows to specify stubbed values and to verify that some calls are expected on your objects. In order to use those functionalities, you need to extend the org.specs2.mock.Mockito trait:

import org.specs2.mock._
class MockitoSpec extends Specification { def is = s2"""

  A java list can be mocked
    You can make it return a stubbed value                                     ${c().stub}
    You can verify that a method was called                                    ${c().verify}
    You can verify that a method was not called                                ${c().verify2}
  """
case class c() extends Mockito {
  val m = mock[java.util.List[String]] // a concrete class would be mocked with: mock[new java.util.LinkedList[String]]
  def stub = {
    m.get(0) returns "one"             // stub a method call with a return value
    m.get(0) must_== "one"             // call the method
  }
  def verify = {
    m.get(0) returns "one"             // stub a method call with a return value
    m.get(0)                           // call the method
    there was one(m).get(0)            // verify that the call happened
  }
  def verify2 = there was no(m).get(0) // verify that the call never happened
  }
}
Creation and settings

Mockito offers the possibility to provide specific settings for the mock being created:

  • its name

val m = mock[List[String]].as("list1")

  • "smart" return values

val m = mock[List[String]].smart

  • "verbose" enables Mockito's verbose logging

val m = mock[List[String]].verbose

  • specific return values

val m = mock[List[String]].defaultReturn(10)

  • specific answers
// a function InvocationOnMock => V is used in place of the org.mockito.stubbing.Answer type for better conciseness
val helloObject = (p1: InvocationOnMock) => "hello "+p1.toString
val m = mock[List[String]].defaultAnswer(helloObject)
  • extra interfaces
val m1 = mock[List[String]].extraInterface[Cloneable]
val m2 = mock[List[String]].extraInterfaces[Cloneable, Serializable]

Now, if you want to combine several of those settings together you need to call the settings method:

val m1 = mock[List[String]].settings(name = "list1",
defaultReturn = 10,
extraInterfaces = classesOf[Cloneable, Serializable])
// or
val m2 = mock[List[String]].settings(smart = true,
extraInterface = classOf[Cloneable])

Finally, in case the Mockito library gets new settings, you can declare the following:

val settings = org.mockito.Mockito.withSettings
val m = mock[List[String]](settings)
Stubbing

Stubbing values is as simple as calling a method on the mock and declaring what should be returned or thrown:

m.get(1) returns "one"
m.get(2) throws new RuntimeException("forbidden")

You can specify different consecutive returned values by appending thenReturns or thenThrows:

m.get(1) returns "one" thenReturns "two"
m.get(2) throws new RuntimeException("forbidden") thenReturns "999"
Mocking and Stubbing at the same time

It is also possible to create a mock while stubbing one of its methods, provided that you declare the type of the expected mock:

val mocked: java.util.List[String] = mock[java.util.List[String]].contains("o") returns true
mocked.contains("o") must beTrue
With matchers

The built-in Mockito argument matchers can be used to specify the method arguments for stubbing:

m.get(anyInt()) returns "element"
m.get(999) must_== "element"

specs2 matchers can also be passed directly as arguments:

m.get(===(123)) returns "one"

Note the call above works because there is an implicit method argThat which transforms a specs2 Matcher[T] into a Hamcrest one and in turn call Mockito's org.mockito.Matchers.argThat method to register the Hamcrest matcher. However sometimes the implicit conversion is not called and you have to explicitly call the argThat method like so:

m.get(argThat(===(123))) returns "one"
Callbacks

In some rare cases, it is necessary to have the return value depend on the parameters passed to the mocked method:

m.get(anyInt) answers { i => "The parameter is " + i.toString }

The function passed to answers will be called with each parameter passed to the stubbed method:

m.get(0)    // returns "The parameter is 0"
m.get(1)    // the second call returns a different value: "The parameter is 1"
Parameters for the answers function

Because of the use of reflection the function passed to answers will receive only instances of the java.lang.Object type.

More precisely, it will:

  • pass the mock object if both the method has no parameters and the function has one parameter:
    mock.size answers { mock => mock.hashCode }
  • pass the parameter if both the method and the function have one parameter:
    mock.get(0) answers { i => i.toString }
  • pass the parameter and the mock object if the method has 1 parameter and the function has 2:
    mock.get(0) answers { (i, mock) => i.toString + " for mock " + mock.toString }

In any other cases, if f is a function of 1 parameter, the array of the method parameters will be passed and if the function has 2 parameters, the second one will be the mock.

Verification

By default Mockito doesn't expect any method to be called. However if your writing interaction-based specifications you want to specify that some methods are indeed called:

there was one(m).get(0)              // one call only to get(0)
there was no(m).get(0)               // no calls to get(0)

// were can also be used
there were two(m).get(0)             // 2 calls exactly to get(0)
there were three(m).get(0)           // 3 calls exactly to get(0)
there were 4.times(m).get(0)         // 4 calls exactly to get(0)

there was atLeastOne(m).get(0)       // at least one call to get(0)
there was atLeastTwo(m).get(0)       // at least two calls to get(0)
there was atLeastThree(m).get(0)     // at least three calls to get(0)
there was atLeast(4)(m).get(0)       // at least four calls to get(0)

there was atMostOne(m).get(0)        // at most one call to get(0)
there was atMostTwo(m).get(0)        // at most two calls to get(0)
there was atMostThree(m).get(0)      // at most three calls to get(0)
there was atMost(4)(m).get(0)        // at most four calls to get(0)

// the combinators above, except `atMost`, can also be used with a timeout
there was after(10.millis).one(m).get(0)
there was after(2.seconds).two(m).get(0)

It is also possible to add all verifications inside a block, when several mocks are involved:

got {
  one(m).get(0)
  two(m).get(1)
}
Order of calls

The order of method calls can be checked by creating calls and chaining them with andThen:

val m1 = mock[List[String]]

m1.get(0)
m1.get(1)

there was one(m1).get(0) andThen one(m1).get(1)

when several mocks are involved, the expected order must be specified as an implicit value:

val m1 = mock[List[String]]
val m2 = mock[List[String]]
val m3 = mock[List[String]]

// the order of mock objects doesn't matter here
implicit val order = inOrder(m1, m3, m2)

m1.get(1); m2.get(2); m3.get(3)

there was one(m1).get(1) andThen one(m2).get(2) andThen one(m3).get(3)
Ignoring stubs

When specifying the behavior of an object in relation to others you may want to verify that some mocks have been called as collaborators and you don't really want to specify what happens to other mocks because they are just playing the role of stubs.

In this case the ignoreStubs method can be used:

val (stub1, stub2) = (mock[AStub], mock[AStub])
there were noMoreCallsTo(ignoreStubs(stub1, stub2))

This method is also available with the inOrder method:

val (list1, list2) = ("", "")

For more documentation about this Mockito functionality, please read here.

Spies

Spies can be used in order to do some "partial mocking" of real objects:

val spiedList = spy(new LinkedList[String])

// methods can be stubbed on a spy
spiedList.size returns 100

// other methods can also be used
spiedList.add("one")
spiedList.add("two")

// and verification can happen on a spy
there was one(spiedList).add("one")

However, working with spies can be tricky:

// if the list is empty, this will throws an IndexOutOfBoundsException
spiedList.get(0) returns "one"

As advised in the Mockito documentation, doReturn must be used in that case:

org.mockito.Mockito.doReturn("one").when(spiedList).get(0)
Functions/PartialFunctions

It is possible to verify method calls where parameters are functions by specifying how the passed function will react to a given set of arguments. Given the following mock:

trait Amount {
// a method showing an amount precision
def show(display: Function2[Double, Int, String]) = ???
}
val amount = mock[Amount]

If the mock is called with this function:

amount.show((amount: Double, precision: Int) => "%2."+precision+"f" format amount)

Then it is possible to verify how the mock was called:

// with sample arguments for the function and the expected result
there was one(amount).show((32.4456, 2) -> "32.45")

// with a matcher for the result
there was one(amount).show((32.4456, 2) -> endWith("45"))

// with any Function2[A, B, R]
there was one(amount).show(anyFunction2)
Auto-boxing

Auto-boxing might interfere with the mocking of PartialFunctions. Please have a look at this for a discussion.

Byname

Byname parameters can be verified but this will not work if the specs2 jar is not put first on the classpath, before the mockito jar. Indeed specs2 redefines a Mockito class for intercepting method calls so that byname parameters are properly handled.

DataTables

DataTables are a very effective way of grouping several similar examples into one. For example, here is how to specify the addition of integers by providing one example on each row of a table:

class DataTableSpec extends Specification with matcher.DataTables { def is = s2"""
  adding integers should just work in scala $e1
"""

  def e1 =
    "a"   | "b" | "c" |                                   // the header of the table, with `|` separated strings
     2    !  2  !  4  |                                   // an example row
     1    !  1  !  2  |> {                                // the > operator to "execute" the table
     (a, b, c) =>  a + b must_== c                        // the expectation to check on each row
    }
}

Implicit !

There may be an implicit definition conflict when the first parameter of a row is a String, because examples can also be created by using the ! operator after a String. In that case, depending on which kind of specification you use, you can either:

  • with an acceptance specification: use the !! operator to disambiguate (and || in the header for good visual balance)
  • with a unit specification: use the org.specs2.mutable.Tables trait instead of org.specs2.matcher.DataTables trait. This will "deactivate" the implicit used to create examples with !

Forms

Forms are a way to represent domain objects or service, and declare expected values in a tabular format. They are supposed to be used with the HtmlRunner to get human-readable documentation.

Forms can be designed as reusable pieces of specification where complex forms can be built out of simple ones.

Outside specs2

The specs2 matchers are a well-delimited piece of functionality that you should be able to reuse in your own test framework. You can reuse the following traits:

  • org.specs2.matcher.MustMatchers (or org.specs2.matcher.ShouldMatchers) to write anything like 1 must be_==(1) and
        get a Result back

  • Important: the MustMatchers trait will fill-in stacktraces on MatchResults while the MustMatchers object will not. This has some important consequences in terms of performances because creating stack traces is expensive

  • You can also use the side-effecting version of that trait called org.specs2.matcher.MustThrownMatchers (or org.specs2.matcher.ShouldThrownMatchers).
        It throws a FailureException as soon as an expectation is failing. Those traits can also be used in a regular
        Specification if you have several expectations per example and if you don't want to chain them with and.

  • Finally, in a JUnit-like library you can use the org.specs2.matcher.JUnitMustMatchers trait which throws
        AssertionFailureErrors

Without any dependency on specs2

The Testing page of the spray project explains how you can define a testing trait in your library which can be used with specs2 or scalatest or any framework defining the following methods:

  • fail(msg: String): Nothing
  • skip(msg: String): Nothing

In specs2, those 2 methods are defined by the org.specs2.matcher.ThrownMessages trait

trait ThrownMessages { this: ThrownExpectations =>
  def fail(m: String): Nothing = failure(m)
  def skip(m: String): Nothing = skipped(m)
}


Total for specification Matchers
Finished in562 ms
Results47 examples, 539 expectations, 0 failure, 0 error