/
HavePropertyMatcher.scala
198 lines (191 loc) · 9.99 KB
/
HavePropertyMatcher.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/* * Copyright 2001-2013 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 scala.reflect.ClassTag
// T is the type of the object that has a property to verify with an instance of this trait, P is the type of that particular property
// Since I should be able to pass
/**
* Trait extended by matcher objects, which may appear after the word <code>have</code>, that can match against a
* property of the type specified by the <code>HavePropertyMatcher</code>'s second type parameter <code>P</code>.
* <code>HavePropertyMatcher</code>'s first type parameter, <code>T</code>, specifies the type that declares the property. The match will succeed if and
* only if the value of the property equals the specified value.
* The object containing the property
* is passed to the <code>HavePropertyMatcher</code>'s
* <code>apply</code> method. The result is a <code>HavePropertyMatchResult[P]</code>.
* A <code>HavePropertyMatcher</code> is, therefore, a function from the specified type, <code>T</code>, to
* a <code>HavePropertyMatchResult[P]</code>.
*
* <p>
* Although <code>HavePropertyMatcher</code>
* and <code>Matcher</code> represent similar concepts, they have no inheritance relationship
* because <code>Matcher</code> is intended for use right after <code>should</code> or <code>must</code>
* whereas <code>HavePropertyMatcher</code> is intended for use right after <code>have</code>.
* </p>
*
* <p>
* A <code>HavePropertyMatcher</code> essentially allows you to write statically typed
* property assertions similar to the dynamic ones that use symbols:
* </p>
*
* <pre class="stHighlighted">
* book should have (<span class="stQuotedString">'title</span> (<span class="stQuotedString">"Moby Dick"</span>)) <span class="stLineComment">// dynamic: uses reflection</span>
* book should have (title (<span class="stQuotedString">"Moby Dick"</span>)) <span class="stLineComment">// type safe: only works on Books; no reflection used</span>
* </pre>
*
* <p>
* 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 that
* includes two methods that produce <code>HavePropertyMatcher</code>s:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">case</span> <span class="stReserved">class</span> <span class="stType">Book</span>(<span class="stReserved">val</span> title: <span class="stType">String</span>, <span class="stReserved">val</span> author: <span class="stType">String</span>)
* <br/><span class="stReserved">trait</span> <span class="stType">CustomMatchers</span> {
* <br/> <span class="stReserved">def</span> title(expectedValue: <span class="stType">String</span>) =
* <span class="stReserved">new</span> <span class="stType">HavePropertyMatcher[Book, String]</span> {
* <span class="stReserved">def</span> apply(book: <span class="stType">Book</span>) =
* <span class="stType">HavePropertyMatchResult</span>(
* book.title == expectedValue,
* <span class="stQuotedString">"title"</span>,
* expectedValue,
* book.title
* )
* }
* <br/> <span class="stReserved">def</span> author(expectedValue: <span class="stType">String</span>) =
* <span class="stReserved">new</span> <span class="stType">HavePropertyMatcher[Book, String]</span> {
* <span class="stReserved">def</span> apply(book: <span class="stType">Book</span>) =
* <span class="stType">HavePropertyMatchResult</span>(
* book.author == expectedValue,
* <span class="stQuotedString">"author"</span>,
* expectedValue,
* book.author
* )
* }
* }
* </pre>
*
* <p>
* Each time the <code>title</code> method is called, it returns a new <code>HavePropertyMatcher[Book, String]</code> that
* can be used to match against the <code>title</code> property of the <code>Book</code> passed to its <code>apply</code>
* method. Because the type parameter of these two <code>HavePropertyMatcher</code>s is <code>Book</code>, they
* can only be used with instances of that type. (The compiler will enforce this.) The match will succeed if the
* <code>title</code> property equals the value passed as <code>expectedValue</code>.
* If the match succeeds, the <code>matches</code> field of the returned <code>HavePropertyMatchResult</code> will be <code>true</code>.
* The second field, <code>propertyName</code>, is simply the string name of the property.
* The third and fourth fields, <code>expectedValue</code> and <code>actualValue</code> indicate the expected and actual
* values, respectively, for the property.
* Here's an example that uses these <code>HavePropertyMatchers</code>:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">class</span> <span class="stType">ExampleSpec</span> <span class="stReserved">extends</span> <span class="stType">RefSpec</span> <span class="stReserved">with</span> <span class="stType">Matchers</span> <span class="stReserved">with</span> <span class="stType">CustomMatchers</span> {
* <br/> describe(<span class="stQuotedString">"A book"</span>) {
* <br/> it(<span class="stQuotedString">"should have the correct title and author"</span>) {
* <br/> <span class="stReserved">val</span> book = <span class="stType">Book</span>(<span class="stQuotedString">"Moby Dick"</span>, <span class="stQuotedString">"Melville"</span>)
* <br/> book should have (
* title (<span class="stQuotedString">"Moby Dick"</span>),
* author (<span class="stQuotedString">"Melville"</span>)
* )
* }
* }
* }
* </pre>
*
* <p>
* These matches should succeed, but if for example the first property, <code>title ("Moby Dick")</code>, were to fail, you would get an error message like:
* </p>
*
* <pre class="stExamples">
* The title property had value "A Tale of Two Cities", instead of its expected value "Moby Dick",
* on object Book(A Tale of Two Cities,Dickens)
* </pre>
*
* <p>
* For more information on <code>HavePropertyMatchResult</code> and the meaning of its fields, please
* see the documentation for <a href="HavePropertyMatchResult.html"><code>HavePropertyMatchResult</code></a>. To understand why <code>HavePropertyMatcher</code>
* is contravariant in its type parameter, see the section entitled "Matcher's variance" in the
* documentation for <a href="../Matcher.html"><code>Matcher</code></a>.
* </p>
*
* @author Bill Venners
*/
trait HavePropertyMatcher[-T, P] extends Function1[T, HavePropertyMatchResult[P]] {
thisHavePropertyMatcher =>
/**
* Check to see if a property on the specified object, <code>objectWithProperty</code>, matches its
* expected value, and report the result in
* the returned <code>HavePropertyMatchResult</code>. The <code>objectWithProperty</code> is
* usually the value to the left of a <code>should</code> or <code>must</code> invocation. For example, <code>book</code>
* would be passed as the <code>objectWithProperty</code> in:
*
* <pre class="stHighlighted">
* book should have (title (<span class="stQuotedString">"Moby Dick"</span>))
* </pre>
*
* @param objectWithProperty the object with the property against which to match
* @return the <code>HavePropertyMatchResult</code> that represents the result of the match
*/
def apply(objectWithProperty: T): HavePropertyMatchResult[P]
/**
* Compose this <code>HavePropertyMatcher</code> with the passed function, returning a new <code>HavePropertyMatcher</code>.
*
* <p>
* This method overrides <code>compose</code> on <code>Function1</code> to
* return a more specific function type of <code>HavePropertyMatcher</code>.
* </p>
*/
override def compose[U](g: U => T): HavePropertyMatcher[U, P] =
new HavePropertyMatcher[U, P] {
def apply(u: U) = thisHavePropertyMatcher.apply(g(u))
}
}
/**
* Companion object for trait <code>HavePropertyMatcher</code> that provides a
* factory method that creates a <code>HavePropertyMatcher[T]</code> from a
* passed function of type <code>(T => HavePropertyMatchResult)</code>.
*
* @author Bill Venners
*/
object HavePropertyMatcher {
/**
* Factory method that creates a <code>HavePropertyMatcher[T]</code> from a
* passed function of type <code>(T => HavePropertyMatchResult)</code>.
*
* <p>
* This allows you to create a <code>HavePropertyMatcher</code> in a slightly
* more concise way, for example:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">case</span> <span class="stReserved">class</span> <span class="stType">Person</span>(name: <span class="stType">String</span>)
* <span class="stReserved">def</span> name(expectedName: <span class="stType">String</span>) = {
* <span class="stType">HavePropertyMatcher</span> {
* (person: <span class="stType">Person</span>) => <span class="stType">HavePropertyMatchResult</span>(
* person.name == expectedName,
* <span class="stQuotedString">"name"</span>,
* expectedName,
* person.name
* )
* }
* </pre>
*
* @author Bill Venners
*/
def apply[T, P](fun: T => HavePropertyMatchResult[P])(implicit evT: ClassTag[T], evP: ClassTag[P]): HavePropertyMatcher[T, P] =
new HavePropertyMatcher[T, P] {
def apply(left: T) = fun(left)
override def toString: String = "HavePropertyMatcher[" + evT.runtimeClass.getName + ", " + evP.runtimeClass.getName + "](" + evT.runtimeClass.getName + " => HavePropertyMatchResult[" + evP.runtimeClass.getName + "])"
}
}