All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 Matchers 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 Matchers, 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 Fruits. * 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 }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy