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

org.scalatest.matchers.Matcher.scala Maven / Gradle / Ivy

There is a newer version: 2.0.M6-SNAP27
Show newest version
/*
 * 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 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). *

* *

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)
 * 
* *

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] { 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) } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy