org.scalatest.matchers.Matcher.scala Maven / Gradle / Ivy
Show all versions of scalatest_2.9.0 Show documentation
/*
* Copyright 2001-2008 Artima, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.scalatest.matchers
import org.scalatest._
/**
* Trait extended by objects that can match a value of the specified type. The value to match is
* passed to the matcher's apply
method. The result is a MatchResult
.
* A matcher is, therefore, a function from the specified type, T
, to a MatchResult
.
*
*
* Creating custom matchers
*
*
* Note: We are planning on adding some new matchers to ScalaTest in a future release, and would like your feedback.
* Please let us know if you have felt the need for a matcher ScalaTest doesn't yet provide, whether or
* not you wrote a custom matcher for it. Please email your feedback to bill AT artima.com.
*
*
*
* If none of the built-in matcher syntax satisfies a particular need you have, you can create
* custom Matcher
s that allow
* you to place your own syntax directly after should
or must
. For example, class java.io.File
has a method exists
, which
* indicates whether a file of a certain path and name exists. Because the exists
method takes no parameters and returns Boolean
,
* you can call it using be
with a symbol or BePropertyMatcher
, yielding assertions like:
*
*
*
* file should be ('exists) // using a symbol
* file should be (inExistance) // using a BePropertyMatcher
*
*
*
* Although these expressions will achieve your goal of throwing a TestFailedException
if the file does not exist, they don't produce
* the most readable code because the English is either incorrect or awkward. In this case, you might want to create a
* custom Matcher[java.io.File]
* named exist
, which you could then use to write expressions like:
*
*
*
* // using a plain-old Matcher
* file should exist
* file should not (exist)
* file should (exist and have ('name ("temp.txt")))
*
*
*
* One good way to organize custom matchers is to place them inside one or more
* traits that you can then mix into the suites or specs that need them. Here's an example:
*
*
*
* trait CustomMatchers {
*
* class FileExistsMatcher extends Matcher[java.io.File] {
*
* def apply(left: java.io.File) = {
*
* val fileOrDir = if (left.isFile) "file" else "directory"
*
* val failureMessageSuffix =
* fileOrDir + " named " + left.getName + " did not exist"
*
* val negatedFailureMessageSuffix =
* fileOrDir + " named " + left.getName + " existed"
*
* MatchResult(
* left.exists,
* "The " + failureMessageSuffix,
* "The " + negatedFailureMessageSuffix,
* "the " + failureMessageSuffix,
* "the " + negatedFailureMessageSuffix
* )
* }
* }
*
* val exist = new FileExistsMatcher
* }
*
* // Make them easy to import with:
* // import CustomMatchers._
* object CustomMatchers extends CustomMatchers
*
*
*
* Note: the CustomMatchers
companion object exists to make it easy to bring the
* matchers defined in this trait into scope via importing, instead of mixing in the trait. The ability
* to import them is useful, for example, when you want to use the matchers defined in a trait in the Scala interpreter console.
*
*
*
* This trait contains one matcher class, FileExistsMatcher
, and a val
named exist
that refers to
* an instance of FileExistsMatcher
. Because the class extends Matcher[java.io.File]
,
* the compiler will only allow it be used to match against instances of java.io.File
. A matcher must declare an
* apply
method that takes the type decared in Matcher
's type parameter, in this case java.io.File
.
* The apply method will return a MatchResult
whose matches
field will indicate whether the match succeeded.
* The failureMessage
field will provide a programmer-friendly error message indicating, in the event of a match failure, what caused
* the match to fail.
*
*
*
* The FileExistsMatcher
matcher in this example determines success by calling exists
on the passed java.io.File
. It
* does this in the first argument passed to the MatchResult
factory method:
*
*
*
* left.exists,
*
*
*
* In other words, if the file exists, this matcher matches.
* The next argument to MatchResult
's factory method produces the failure message string:
*
*
*
* "The " + failureMessageSuffix,
*
*
*
* If the passed java.io.File
is a file (not a directory) and has the name temp.txt
, for example, the failure
* message would be:
*
*
*
* The file named temp.txt did not exist
*
*
*
* For more information on the fields in a MatchResult
, including the subsequent three fields that follow the failure message,
* please see the documentation for MatchResult
.
*
*
*
* Given the CustomMatchers
trait as defined above, you can use the exist
syntax in any suite or spec in
* which you mix in the trait:
*
*
*
* class ExampleSpec extends Spec with ShouldMatchers with CustomMatchers {
*
* describe("A temp file") {
*
* it("should be created and deleted") {
*
* val tempFile = java.io.File.createTempFile("delete", "me")
*
* try {
* // At this point the temp file should exist
* tempFile should exist
* }
* finally {
* tempFile.delete()
* }
*
* // At this point it should not exist
* tempFile should not (exist)
* }
* }
* }
*
*
*
* Note that when you use custom Matcher
s, you will need to put parentheses around the custom matcher when if follows not
,
* as shown in the last assertion above: tempFile should not (exist)
.
*
*
* Other ways to create matchers
*
*
* There are other ways to create new matchers besides defining one as shown above. For example, you would normally check to ensure
* an option is defined like this:
*
*
*
* Some("hi") should be ('defined)
*
*
*
* If you wanted to get rid of the tick mark, you could simply define defined
like this:
*
*
*
* val defined = 'defined
*
*
*
* Now you can check that an option is defined without the tick mark:
*
*
*
* Some("hi") should be (defined)
*
*
*
* Perhaps after using that for a while, you realize you're tired of typing the parentheses. You could
* get rid of them with another one-liner:
*
*
*
* val beDefined = be (defined)
*
*
*
* Now you can check that an option is defined without the tick mark or the parentheses:
*
*
*
* Some("hi") should beDefined
*
*
*
* You can also use ScalaTest matchers' logical operators to combine existing matchers into new ones, like this:
*
*
*
* val beWithinTolerance = be >= 0 and be <= 10
*
*
*
* Now you could check that a number is within the tolerance (in this case, between 0 and 10, inclusive), like this:
*
*
*
* num should beWithinTolerance
*
*
*
* When defining a full blown matcher, one shorthand is to use one of the factory methods in Matcher
's companion
* object. For example, instead of writing this:
*
*
*
* val beOdd =
* new Matcher[Int] {
* def apply(left: Int) =
* MatchResult(
* left % 2 == 1,
* left + " was not odd",
* left + " was odd"
* )
* }
*
*
*
* You could alternately write this:
*
*
*
* val beOdd =
* Matcher { (left: Int) =>
* MatchResult(
* left % 2 == 1,
* left + " was not odd",
* left + " was odd"
* )
* }
*
*
*
* Either way you define the beOdd
matcher, you could use it like this:
*
*
*
* 3 should beOdd
* 4 should not (beOdd)
*
*
*
* You can also compose matchers. If for some odd reason, you wanted a Matcher[String]
that
* checked whether a string, when converted to an Int
,
* was odd, you could make one by composing beOdd
with
* a function that converts a string to an Int
, like this:
*
*
*
* val beOddAsInt = beOdd compose { (s: String) => s.toInt }
*
*
*
* Now you have a Matcher[String]
whose apply
method first
* invokes the converter function to convert the passed string to an Int
,
* then passes the resulting Int
to beOdd
. Thus, you could use
* beOddAsInt
like this:
*
*
*
* "3" should beOddAsInt
* "4" should not (beOddAsInt)
*
*
*
* You can also define a method that produces a matcher using matcher
* composition and a passed parameter. For example, here's how you could create a Matcher[File]
from a
* Matcher[String]
:
*
*
*
* import java.io.File
*
* def endWithExtension(ext: String) = endWith(ext) compose { (f: File) => f.getPath }
*
*
*
* Every time you call the above endWithExtension
method, you'll get a new Matcher[File]
. Here's an example:
*
*
*
* new File("output.txt") should endWithExtension("txt")
*
*
* Matcher's variance
*
*
* Matcher
is contravariant in its type parameter, T
, to make its use more flexible.
* As an example, consider the hierarchy:
*
*
*
* class Fruit
* class Orange extends Fruit
* class ValenciaOrange extends Orange
*
*
*
* Given an orange:
*
*
*
* val orange = Orange
*
*
*
* The expression "orange should
" will, via an implicit conversion in ShouldMatchers
,
* result in an object that has a should
* method that takes a Matcher[Orange]
. If the static type of the matcher being passed to should
is
* Matcher[Valencia]
it shouldn't (and won't) compile. The reason it shouldn't compile is that
* the left value is an Orange
, but not necessarily a Valencia
, and a
* Matcher[Valencia]
only knows how to match against a Valencia
. The reason
* it won't compile is given that Matcher
is contravariant in its type parameter, T
, a
* Matcher[Valencia]
is not a subtype of Matcher[Orange]
.
*
*
*
* By contrast, if the static type of the matcher being passed to should
is Matcher[Fruit]
,
* it should (and will) compile. The reason it should compile is that given the left value is an Orange
,
* it is also a Fruit
, and a Matcher[Fruit]
knows how to match against Fruit
s.
* The reason it will compile is that given that Matcher
is contravariant in its type parameter, T
, a
* Matcher[Fruit]
is indeed a subtype of Matcher[Orange]
.
*
*
* @author Bill Venners
*/
trait Matcher[-T] extends Function1[T, MatchResult] { thisMatcher =>
/**
* Check to see if the specified object, left
, matches, and report the result in
* the returned MatchResult
. The parameter is named left
, because it is
* usually the value to the left of a should
or must
invocation. For example,
* in:
*
*
* list should equal (List(1, 2, 3))
*
*
* The equal (List(1, 2, 3))
expression results in a matcher that holds a reference to the
* right value, List(1, 2, 3)
. The should
method invokes apply
* on this matcher, passing in list
, which is therefore the "left
" value. The
* matcher will compare the list
(the left
value) with List(1, 2, 3)
(the right
* value), and report the result in the returned MatchResult
.
*
* @param left the value against which to match
* @return the MatchResult
that represents the result of the match
*/
def apply(left: T): MatchResult
/**
* Compose this matcher with the passed function, returning a new matcher.
*
*
* This method overrides compose
on Function1
to
* return a more specific function type of Matcher
. For example, given
* a beOdd
matcher defined like this:
*
*
*
* val beOdd =
* new Matcher[Int] {
* def apply(left: Int) =
* MatchResult(
* left % 2 == 1,
* left + " was not odd",
* left + " was odd"
* )
* }
*
*
*
* You could use beOdd
like this:
*
*
*
* 3 should beOdd
* 4 should not (beOdd)
*
*
*
* If for some odd reason, you wanted a Matcher[String]
that
* checked whether a string, when converted to an Int
,
* was odd, you could make one by composing beOdd
with
* a function that converts a string to an Int
, like this:
*
*
*
* val beOddAsInt = beOdd compose { (s: String) => s.toInt }
*
*
*
* Now you have a Matcher[String]
whose apply
method first
* invokes the converter function to convert the passed string to an Int
,
* then passes the resulting Int
to beOdd
. Thus, you could use
* beOddAsInt
like this:
*
*
*
* "3" should beOddAsInt
* "4" should not (beOddAsInt)
*
*/
override def compose[U](g: U => T): Matcher[U] =
new Matcher[U] {
def apply(u: U) = thisMatcher.apply(g(u))
}
}
/**
* Companion object for trait Matcher
that provides a
* factory method that creates a Matcher[T]
from a
* passed function of type (T => MatchResult)
.
*
* @author Bill Venners
*/
object Matcher {
/**
* Factory method that creates a Matcher[T]
from a
* passed function of type (T => MatchResult)
.
*
* @author Bill Venners
*/
def apply[T](fun: T => MatchResult): Matcher[T] =
new Matcher[T] {
def apply(left: T) = fun(left)
}
}