org.scalatest.matchers.Matcher.scala Maven / Gradle / Ivy
/*
* 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
*
*
* If none of the built-in matcher syntax satisfy 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)
.
*
*
* 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] {
/**
* 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
}