1/*
2 * Copyright 2001-2008 Artima, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.scalatest.matchers
17
18import org.scalatest._
19import org.scalatest.prop.Checkers
20import org.scalacheck._
21import Arbitrary._
22import Prop._
23import scala.reflect.BeanProperty
24
25// TODO: check not not and not not not to make sure those negative failure messages make sense.
26class ShouldHavePropertiesSpec extends Spec with ShouldMatchers with Checkers with ReturnsNormallyThrowsAssertion with BookPropertyMatchers {
27
28  // Checking for a specific size
29  describe("The 'have (' syntax") {
30
31    describe("on an object with properties") {
32
33      val book = new Book("A Tale of Two Cities", "Dickens", 1859, 45, true)
34      val badBook = new Book("A Tale of Two Cities", "Dickens", 1859, 45, false)
35      // val bookshelf = new Bookshelf(book, badBook, book)
36
37      it("should do nothing if there's just one property and it matches") {
38        book should have (title ("A Tale of Two Cities"))
39        book should have ('title ("A Tale of Two Cities"))
40      }
41
42      it("should do nothing if all the properties match") {
43        book should have (
44          title ("A Tale of Two Cities"),
45          author ("Dickens"),
46          pubYear (1859)
47        )
48        book should have (
49          'title ("A Tale of Two Cities"),
50          'author ("Dickens"),
51          'pubYear (1859)
52        )
53      }
54
55      it("should do nothing if there's just one property and it does not match, when used with not") {
56        book should not have (title ("One Hundred Years of Solitude"))
57        book should not have ('title ("One Hundred Years of Solitude"))
58      }
59
60      // title/author matches | have | have not
61      // 0 0 | 0 | 1
62      // 0 1 | 0 | 1
63      // 1 0 | 0 | 1
64      // 1 1 | 1 | 0
65      it("should do nothing if at least one of the properties does not match, when used with not") {
66
67        // 0 0
68        book should not have (
69          title ("Moby Dick"),
70          author ("Melville")
71        )
72        book should not have (
73          'title ("Moby Dick"),
74          'author ("Melville")
75        )
76
77        // 0 1
78        book should not have (
79          title ("Moby Dick"),
80          author ("Dickens")
81        )
82        book should not have (
83          'title ("Moby Dick"),
84          'author ("Dickens")
85        )
86
87        // 1 0
88        book should not have (
89          title ("A Tale of Two Cities"),
90          author ("Melville")
91        )
92        book should not have (
93          'title ("A Tale of Two Cities"),
94          'author ("Melville")
95        )
96      }
97
98      it("should do nothing if all properties match, when used with and") {
99        book should (have (title ("A Tale of Two Cities")) and (have (author ("Dickens"))))
100        book should (have (title ("A Tale of Two Cities")) and have (author ("Dickens")))
101        book should (have ('title ("A Tale of Two Cities")) and (have ('author ("Dickens"))))
102        book should (have ('title ("A Tale of Two Cities")) and have ('author ("Dickens")))
103      }
104
105      it("should do nothing if at least one property matches, when used with or") {
106
107        // both true
108        book should (have (title ("A Tale of Two Cities")) or (have (author ("Dickens"))))
109        book should (have (title ("A Tale of Two Cities")) or have (author ("Dickens")))
110        book should (have ('title ("A Tale of Two Cities")) or (have ('author ("Dickens"))))
111        book should (have ('title ("A Tale of Two Cities")) or have ('author ("Dickens")))
112
113        // first true
114        book should (have (title ("A Tale of Two Cities")) or (have (author ("Melville"))))
115        book should (have (title ("A Tale of Two Cities")) or have (author ("Melville")))
116        book should (have ('title ("A Tale of Two Cities")) or (have ('author ("Melville"))))
117        book should (have ('title ("A Tale of Two Cities")) or have ('author ("Melville")))
118
119        // second true
120        book should (have (title ("Moby Dick")) or (have (author ("Dickens"))))
121        book should (have (title ("Moby Dick")) or have (author ("Dickens")))
122        book should (have ('title ("Moby Dick")) or (have ('author ("Dickens"))))
123        book should (have ('title ("Moby Dick")) or have ('author ("Dickens")))
124      }
125
126      it("should do nothing if no properties match, when used with and and not") {
127
128        // just one property
129        book should (not have (title ("Moby Dick")) and (not have (author ("Melville"))))
130        book should (not have (title ("Moby Dick")) and not (have (author ("Melville"))))
131        book should (not have (title ("Moby Dick")) and not have (author ("Melville")))
132        book should (not have ('title ("Moby Dick")) and (not have ('author ("Melville"))))
133        book should (not have ('title ("Moby Dick")) and not (have ('author ("Melville"))))
134        book should (not have ('title ("Moby Dick")) and not have ('author ("Melville")))
135
136        // multiple properties
137        book should (not have (title ("Moby Dick"), pubYear (1859)) and (not have (pubYear (1859), author ("Melville"))))
138        book should (not have (title ("Moby Dick"), pubYear (1859)) and not (have (pubYear (1859), author ("Melville"))))
139        book should (not have (title ("Moby Dick"), pubYear (1859)) and not have (pubYear (1859), author ("Melville")))
140        book should (not have ('title ("Moby Dick"), pubYear (1859)) and (not have ('pubYear (1859), 'author ("Melville"))))
141        book should (not have ('title ("Moby Dick"), pubYear (1859)) and not (have ('pubYear (1859), 'author ("Melville"))))
142        book should (not have ('title ("Moby Dick"), pubYear (1859)) and not have ('pubYear (1859), 'author ("Melville")))
143      }
144
145      it("should do nothing if no properties match, when used with or and not") {
146
147        // both true
148        // just one property
149        book should (not have (title ("Moby Dick")) or (not have (author ("Melville"))))
150        book should (not have (title ("Moby Dick")) or not (have (author ("Melville"))))
151        book should (not have (title ("Moby Dick")) or not have (author ("Melville")))
152        book should (not have ('title ("Moby Dick")) or (not have ('author ("Melville"))))
153        book should (not have ('title ("Moby Dick")) or not (have ('author ("Melville"))))
154        book should (not have ('title ("Moby Dick")) or not have ('author ("Melville")))
155
156        // multiple properties
157        book should (not have (title ("Moby Dick"), pubYear (1859)) or (not have (pubYear (1859), author ("Melville"))))
158        book should (not have (title ("Moby Dick"), pubYear (1859)) or not (have (pubYear (1859), author ("Melville"))))
159        book should (not have (title ("Moby Dick"), pubYear (1859)) or not have (pubYear (1859), author ("Melville")))
160        book should (not have ('title ("Moby Dick"), pubYear (1859)) or (not have ('pubYear (1859), 'author ("Melville"))))
161        book should (not have ('title ("Moby Dick"), pubYear (1859)) or not (have ('pubYear (1859), 'author ("Melville"))))
162        book should (not have ('title ("Moby Dick"), pubYear (1859)) or not have ('pubYear (1859), 'author ("Melville")))
163
164        // first true
165        // just one property
166        book should (not have (title ("Moby Dick")) or (not have (author ("Dickens"))))
167        book should (not have (title ("Moby Dick")) or not (have (author ("Dickens"))))
168        book should (not have (title ("Moby Dick")) or not have (author ("Dickens")))
169        book should (not have ('title ("Moby Dick")) or (not have ('author ("Dickens"))))
170        book should (not have ('title ("Moby Dick")) or not (have ('author ("Dickens"))))
171        book should (not have ('title ("Moby Dick")) or not have ('author ("Dickens")))
172
173        // multiple properties
174        book should (not have (title ("Moby Dick"), pubYear (1859)) or (not have (pubYear (1859), author ("Dickens"))))
175        book should (not have (title ("Moby Dick"), pubYear (1859)) or not (have (pubYear (1859), author ("Dickens"))))
176        book should (not have (title ("Moby Dick"), pubYear (1859)) or not have (pubYear (1859), author ("Dickens")))
177        book should (not have ('title ("Moby Dick"), pubYear (1859)) or (not have ('pubYear (1859), 'author ("Dickens"))))
178        book should (not have ('title ("Moby Dick"), pubYear (1859)) or not (have ('pubYear (1859), 'author ("Dickens"))))
179        book should (not have ('title ("Moby Dick"), pubYear (1859)) or not have ('pubYear (1859), 'author ("Dickens")))
180
181        // second true
182        // just one property
183        book should (not have (title ("A Tale of Two Cities")) or (not have (author ("Melville"))))
184        book should (not have (title ("A Tale of Two Cities")) or not (have (author ("Melville"))))
185        book should (not have (title ("A Tale of Two Cities")) or not have (author ("Melville")))
186        book should (not have ('title ("A Tale of Two Cities")) or (not have ('author ("Melville"))))
187        book should (not have ('title ("A Tale of Two Cities")) or not (have ('author ("Melville"))))
188        book should (not have ('title ("A Tale of Two Cities")) or not have ('author ("Melville")))
189
190        // multiple properties
191        book should (not have (title ("A Tale of Two Cities"), pubYear (1859)) or (not have (pubYear (1859), author ("Melville"))))
192        book should (not have (title ("A Tale of Two Cities"), pubYear (1859)) or not (have (pubYear (1859), author ("Melville"))))
193        book should (not have (title ("A Tale of Two Cities"), pubYear (1859)) or not have (pubYear (1859), author ("Melville")))
194        book should (not have ('title ("A Tale of Two Cities"), pubYear (1859)) or (not have ('pubYear (1859), 'author ("Melville"))))
195        book should (not have ('title ("A Tale of Two Cities"), pubYear (1859)) or not (have ('pubYear (1859), 'author ("Melville"))))
196        book should (not have ('title ("A Tale of Two Cities"), pubYear (1859)) or not have ('pubYear (1859), 'author ("Melville")))
197      }
198
199      it("should throw TestFailedException if trying to check for a non existent property") {
200        val thrown = evaluating {
201          new Object should have ('nonExistentProperty ("something"))
202        } should produce [TestFailedException]
203        thrown.getMessage should equal("have nonExistentProperty (something) used with an object that had no public field or method named nonExistentProperty or getNonExistentProperty")
204      }
205
206      it("should throw TestFailedException if there's just one property and it doesn't match") {
207
208        val caught1 = intercept[TestFailedException] {
209          book should have (author ("Gibson"))
210        }
211        assert(caught1.getMessage === "The author property had value \"Dickens\", instead of its expected value \"Gibson\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
212
213        val caught2 = intercept[TestFailedException] {
214          book should have ('author ("Gibson"))
215        }
216        assert(caught2.getMessage === "The author property had value \"Dickens\", instead of its expected value \"Gibson\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
217      }
218
219      it("should throw TestFailedException if at least one of the properties doesn't match") {
220
221        val caught1 = intercept[TestFailedException] {
222          book should have (
223            title ("A Tale of Two Cities"),
224            author ("Gibson"),
225            pubYear (1859)
226          )
227        }
228        assert(caught1.getMessage === "The author property had value \"Dickens\", instead of its expected value \"Gibson\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
229
230        val caught2 = intercept[TestFailedException] {
231          book should have (
232            title ("A Tale of Two Cities"),
233            'author ("Gibson"),
234            pubYear (1859)
235          )
236        }
237        assert(caught2.getMessage === "The author property had value \"Dickens\", instead of its expected value \"Gibson\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
238
239        val caught3 = intercept[TestFailedException] {
240          book should have (
241            'title ("A Tale of Two Cities"),
242            'author ("Dickens"),
243            'pubYear (1959)
244          )
245        }
246        assert(caught3.getMessage === "The pubYear property had value 1859, instead of its expected value 1959, on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
247      }
248
249      it("should throw TestFailedException if there's just one property and it matches, when used with not") {
250
251        val caught1 = intercept[TestFailedException] {
252          book should not have (author ("Dickens"))
253        }
254        assert(caught1.getMessage === "The author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
255
256        val caught2 = intercept[TestFailedException] {
257          book should not have ('author ("Dickens"))
258        }
259        assert(caught2.getMessage === "The author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
260      }
261
262      /*
263      Not (matcher) needs to yield the opposite result as (matcher) itself, and
264      that means that not (matcher) will be true if at least one
265
266      title/author/pubYear matches | have | not have
267      0 0 0 | 0 | 1
268      0 0 1 | 0 | 1
269      0 1 0 | 0 | 1
270      0 1 1 | 0 | 1
271      1 0 0 | 0 | 1
272      1 0 1 | 0 | 1
273      1 1 0 | 0 | 1
274      1 1 1 | 1 | 0
275
276      So 'not have" means that at least one is false, not all are false.
277
278      To reduce the number of tests cases just use two:
279
280      title/author matches | have | have not
281      0 0 | 0 | 1
282      0 1 | 0 | 1
283      1 0 | 0 | 1
284      1 1 | 1 | 0
285
286
287      have matches (1 1) all properties matched.
288      have does not match (0 0, 0 1, 1 0) the (first property found that doesn't match) didn't match
289      not have matches (0 0, 0 1, 1 0) the (first property found that doesn't match), as expected
290      not have does not match (1, 1) all properties matched.
291      */
292      it("should throw TestFailedException if all of the properties match, when used with not") {
293        val caught1 = intercept[TestFailedException] {
294          book should not have (
295            title ("A Tale of Two Cities"),
296            author ("Dickens")
297          )
298        }
299        assert(caught1.getMessage === "All properties had their expected values, respectively, on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
300      }
301
302      it("should throw TestFailedException if at least one property does not match, when used with and") {
303
304        // second false
305        val caught1 = intercept[TestFailedException] {
306          book should (have (title ("A Tale of Two Cities")) and (have (author ("Melville"))))
307        }
308        assert(caught1.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true), but the author property had value \"Dickens\", instead of its expected value \"Melville\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
309
310        val caught2 = intercept[TestFailedException] {
311          book should (have (title ("A Tale of Two Cities")) and have (author ("Melville")))
312        }
313        assert(caught2.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true), but the author property had value \"Dickens\", instead of its expected value \"Melville\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
314
315        val caught3 = intercept[TestFailedException] {
316          book should (have ('title ("A Tale of Two Cities")) and (have ('author ("Melville"))))
317        }
318        assert(caught3.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true), but the author property had value \"Dickens\", instead of its expected value \"Melville\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
319
320        val caught4 = intercept[TestFailedException] {
321          book should (have ('title ("A Tale of Two Cities")) and have ('author ("Melville")))
322        }
323        assert(caught4.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true), but the author property had value \"Dickens\", instead of its expected value \"Melville\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
324
325        // first false
326        val caught11 = intercept[TestFailedException] {
327          book should (have (title ("Moby Dick")) and (have (author ("Dickens"))))
328        }
329        assert(caught11.getMessage === "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,1859,45,true)")
330
331        val caught12 = intercept[TestFailedException] {
332          book should (have (title ("Moby Dick")) and have (author ("Dickens")))
333        }
334        assert(caught12.getMessage === "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,1859,45,true)")
335
336        val caught13 = intercept[TestFailedException] {
337          book should (have ('title ("Moby Dick")) and (have ('author ("Dickens"))))
338        }
339        assert(caught13.getMessage === "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,1859,45,true)")
340
341        val caught14 = intercept[TestFailedException] {
342          book should (have ('title ("Moby Dick")) and have ('author ("Dickens")))
343        }
344        assert(caught14.getMessage === "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,1859,45,true)")
345
346        // both false
347        val caught21 = intercept[TestFailedException] {
348          book should (have (title ("Moby Dick")) and (have (author ("Melville"))))
349        }
350        assert(caught21.getMessage === "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,1859,45,true)")
351
352        val caught22 = intercept[TestFailedException] {
353          book should (have (title ("Moby Dick")) and have (author ("Melville")))
354        }
355        assert(caught22.getMessage === "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,1859,45,true)")
356
357        val caught23 = intercept[TestFailedException] {
358          book should (have ('title ("Moby Dick")) and (have ('author ("Melville"))))
359        }
360        assert(caught23.getMessage === "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,1859,45,true)")
361
362        val caught24 = intercept[TestFailedException] {
363          book should (have ('title ("Moby Dick")) and have ('author ("Melville")))
364        }
365        assert(caught24.getMessage === "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,1859,45,true)")
366      }
367
368      it("should throw TestFailedException if neither property matches, when used with or") {
369
370        // both false
371        val caught21 = intercept[TestFailedException] {
372          book should (have (title ("Moby Dick")) or (have (author ("Melville"))))
373        }
374        assert(caught21.getMessage === "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,1859,45,true), and the author property had value \"Dickens\", instead of its expected value \"Melville\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
375
376        val caught22 = intercept[TestFailedException] {
377          book should (have (title ("Moby Dick")) or have (author ("Melville")))
378        }
379        assert(caught22.getMessage === "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,1859,45,true), and the author property had value \"Dickens\", instead of its expected value \"Melville\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
380
381        val caught23 = intercept[TestFailedException] {
382          book should (have ('title ("Moby Dick")) or (have ('author ("Melville"))))
383        }
384        assert(caught23.getMessage === "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,1859,45,true), and the author property had value \"Dickens\", instead of its expected value \"Melville\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
385
386        val caught24 = intercept[TestFailedException] {
387          book should (have ('title ("Moby Dick")) or have ('author ("Melville")))
388        }
389        assert(caught24.getMessage === "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,1859,45,true), and the author property had value \"Dickens\", instead of its expected value \"Melville\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
390      }
391
392      it("should throw TestFailedException if at least one property does not match, when used with and and not") {
393
394        // second false
395        val caught1 = intercept[TestFailedException] {
396          book should (not have (title ("A Tale of Two Cities")) and not (have (author ("Melville"))))
397        }
398        assert(caught1.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
399
400        val caught2 = intercept[TestFailedException] {
401          book should (not have (title ("A Tale of Two Cities")) and not have (author ("Melville")))
402        }
403        assert(caught2.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
404
405        val caught3 = intercept[TestFailedException] {
406          book should (not have ('title ("A Tale of Two Cities")) and not (have ('author ("Melville"))))
407        }
408        assert(caught3.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
409
410        val caught4 = intercept[TestFailedException] {
411          book should (not have ('title ("A Tale of Two Cities")) and not have ('author ("Melville")))
412        }
413        assert(caught4.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
414
415        val caught5 = intercept[TestFailedException] {
416          book should (not have (title ("A Tale of Two Cities")) and (not have (author ("Melville"))))
417        }
418        assert(caught5.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
419
420        val caught6 = intercept[TestFailedException] {
421          book should (not have ('title ("A Tale of Two Cities")) and (not have ('author ("Melville"))))
422        }
423        assert(caught6.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
424
425        // first false
426        val caught11 = intercept[TestFailedException] {
427          book should (not have (title ("Moby Dick")) and not (have (author ("Dickens"))))
428        }
429        assert(caught11.getMessage === "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,1859,45,true), but the author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
430
431        val caught12 = intercept[TestFailedException] {
432          book should (not have (title ("Moby Dick")) and not have (author ("Dickens")))
433        }
434        assert(caught12.getMessage === "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,1859,45,true), but the author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
435
436        val caught13 = intercept[TestFailedException] {
437          book should (not have ('title ("Moby Dick")) and (not have ('author ("Dickens"))))
438        }
439        assert(caught13.getMessage === "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,1859,45,true), but the author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
440
441        val caught14 = intercept[TestFailedException] {
442          book should (not have ('title ("Moby Dick")) and not have ('author ("Dickens")))
443        }
444        assert(caught14.getMessage === "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,1859,45,true), but the author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
445
446        val caught15 = intercept[TestFailedException] {
447          book should (not have (title ("Moby Dick")) and (not have (author ("Dickens"))))
448        }
449        assert(caught15.getMessage === "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,1859,45,true), but the author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
450
451        val caught16 = intercept[TestFailedException] {
452          book should (not have ('title ("Moby Dick")) and (not have ('author ("Dickens"))))
453        }
454        assert(caught16.getMessage === "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,1859,45,true), but the author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
455
456        // both true
457        val caught21 = intercept[TestFailedException] {
458          book should (not have (title ("A Tale of Two Cities")) and (not have (author ("Dickens"))))
459        }
460        assert(caught21.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
461
462        val caught22 = intercept[TestFailedException] {
463          book should (not have (title ("A Tale of Two Cities")) and not have (author ("Dickens")))
464        }
465        assert(caught22.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
466
467        val caught23 = intercept[TestFailedException] {
468          book should (not have ('title ("A Tale of Two Cities")) and (not have ('author ("Dickens"))))
469        }
470        assert(caught23.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
471
472        val caught24 = intercept[TestFailedException] {
473          book should (not have ('title ("A Tale of Two Cities")) and not have ('author ("Dickens")))
474        }
475        assert(caught24.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
476
477        val caught25 = intercept[TestFailedException] {
478          book should (not have (title ("A Tale of Two Cities")) and (not (have (author ("Dickens")))))
479        }
480        assert(caught25.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
481
482        val caught26 = intercept[TestFailedException] {
483          book should (not have ('title ("A Tale of Two Cities")) and (not (have ('author ("Dickens")))))
484        }
485        assert(caught26.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
486      }
487
488      it("should throw TestFailedException if both properties match, when used with or and not") {
489
490        // both false
491        val caught21 = intercept[TestFailedException] {
492          book should (not have (title ("A Tale of Two Cities")) or (not have (author ("Dickens"))))
493        }
494        assert(caught21.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true), and the author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
495
496        val caught22 = intercept[TestFailedException] {
497          book should (not have (title ("A Tale of Two Cities")) or not have (author ("Dickens")))
498        }
499        assert(caught22.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true), and the author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
500
501        val caught23 = intercept[TestFailedException] {
502          book should (not have ('title ("A Tale of Two Cities")) or (not have ('author ("Dickens"))))
503        }
504        assert(caught23.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true), and the author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
505
506        val caught24 = intercept[TestFailedException] {
507          book should (not have ('title ("A Tale of Two Cities")) or not have ('author ("Dickens")))
508        }
509        assert(caught24.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true), and the author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
510
511        val caught25 = intercept[TestFailedException] {
512          book should (not have (title ("A Tale of Two Cities")) or (not have (author ("Dickens"))))
513        }
514        assert(caught25.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true), and the author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
515
516        val caught26 = intercept[TestFailedException] {
517          book should (not have ('title ("A Tale of Two Cities")) or (not have ('author ("Dickens"))))
518        }
519        assert(caught26.getMessage === "The title property had its expected value \"A Tale of Two Cities\", on object Book(A Tale of Two Cities,Dickens,1859,45,true), and the author property had its expected value \"Dickens\", on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
520
521        // A double one, so that I can see the mid-sentence version of the 'all properties...' error message
522        val caught31 = intercept[TestFailedException] {
523          book should (
524            not have (
525              title ("A Tale of Two Cities"),
526              author ("Dickens")
527            ) or not have (
528              author ("Dickens"),
529              pubYear (1859)
530            )
531          )
532        }
533        assert(caught31.getMessage === "All properties had their expected values, respectively, on object Book(A Tale of Two Cities,Dickens,1859,45,true), and all properties had their expected values, respectively, on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
534      }
535
536/*
537The book1 result class doesn't compile in 2.8, and rightly so. It had a type error that the 2.7 compiler didn't find. Had already
538decided that I didn't like nesting, so not too concerned if there's not a way for it to work. Trouble is that it looks too
539hard to read. Better to have people pull things out and then just do a non-nested match on that. More readable.
540      it("should throw TestFailedException if a nested property matcher expression is used and a nested property doesn't match") {
541
542        // I'm not too hot on this syntax, but can't prevent it and wouldn't want to. If people want do to nested property
543        // checks, they can do it this way.
544        val caught1 = intercept[TestFailedException] {
545          bookshelf should have (
546            book1 (
547              title ("A Tale of Two Cities"),
548              author ("Gibson"),
549              pubYear (1859)
550            )
551          )
552        }
553        assert(caught1.getMessage === "The book1.author property had value \"Dickens\", instead of its expected value \"Gibson\", on object Bookshelf(Book(A Tale of Two Cities,Dickens,1859,45,true),Book(A Tale of Two Cities,Dickens,1859,45,false),Book(A Tale of Two Cities,Dickens,1859,45,true))")
554      }
555*/
556
557      it("should work with length not a symbol without anything special, in case someone forgets you don't need the parens with length") {
558
559        val caught1 = intercept[TestFailedException] {
560          book should have (length (43))
561        }
562        assert(caught1.getMessage === "The length property had value 45, instead of its expected value 43, on object Book(A Tale of Two Cities,Dickens,1859,45,true)")
563      }
564
565      it("should throw TestFailedException if length used in parens but the length property is not an integral type") {
566
567        class LengthSeven {
568          def length = "seven"
569        }
570
571        val caught1 = intercept[TestFailedException] {
572          (new LengthSeven) should have (length (43))
573        }
574        assert(caught1.getMessage === "The length property was none of Byte, Short, Int, or Long.")
575      }
576
577      it("should work with size not a symbol without anything special, in case someone forgets you don't need the parens with size") {
578
579        case class Size(val size: Int)
580
581        val caught1 = intercept[TestFailedException] {
582          (new Size(7)) should have (size (43))
583        }
584        assert(caught1.getMessage === "The size property had value 7, instead of its expected value 43, on object Size(7)")
585      }
586
587      it("should throw TestFailedException if size used in parens but the size property is not an integral type") {
588
589        class SizeSeven {
590          def size = "seven"
591        }
592
593        val caught1 = intercept[TestFailedException] {
594          (new SizeSeven) should have (size (43))
595        }
596        assert(caught1.getMessage === "The size property was none of Byte, Short, Int, or Long.")
597      }
598
599/*
600I decided not to support this syntax in 0.9.5, and maybe never. It is not clear to me that it is
601readable enough. I can't prevent someone from making HavePropertyMatchers to do this kind of thing,
602and that's fine. It actually gives them a way to do it if they want to do it.
603      it("should throw TestFailedException if a nested property matcher expression with a symbol is used and a nested property doesn't match") {
604
605        val caught1 = intercept[TestFailedException] {
606          bookshelf should have (
607            'book1 (
608              title ("A Tale of Two Cities"),
609              author ("Gibson"),
610              pubYear (1859)
611            )
612          )
613        }
614        assert(caught1.getMessage === "expected property book1.author to have value \"Gibson\", but it had value \"Dickens\"")
615      }
616*/
617
618      /*
619      This does not compile, which is what I want
620      it("should not compile if you don't enter any verifiers") {
621        book should have ()
622      }
623      */
624    }
625  }
626
627  describe("the compose method on HavePropertyMatcher") {
628    it("should return another HavePropertyMatcher") {
629      val book1 = new Book("A Tale of Two Cities", "Dickens", 1859, 45, true)
630      val book2 = new Book("The Handmaid's Tail", "Atwood", 1985, 200, true)
631      val badBook = new Book("Some Bad Book", "Bad Author", 1999, 150, false)
632      case class Library(books: List[Book])
633      val goodLibrary = Library(List(book1, book2))
634      val badLibrary = Library(List(badBook, book1, book2))
635
636      def goodBooksToRead(expectedValue: Boolean) =
637        new GoodReadMatcher(expectedValue) compose { (lib: Library) => lib.books.head }
638
639      goodLibrary should have (goodBooksToRead(true))
640      badLibrary should not be (goodBooksToRead(true))
641    }
642  }
643
644  describe("A factory method on HavePropertyMatcher's companion object") {
645    it("should produce a have-matcher that executes the passed function when its apply is called") {
646      case class Person(name: String)
647      def name(expectedName: String) = {
648        HavePropertyMatcher {
649          (person: Person) => HavePropertyMatchResult(
650            person.name == expectedName,
651            "name",
652            expectedName,
653            person.name
654          )
655        }
656      }
657      Person("Bob") should have (name("Bob"))
658      Person("Sally") should not have (name("George"))
659      Person("Cindy") should have (name("Cindy"))
660      Person("Doug") should have (name("Doug"))
661      Person("Alicia") should have (name("Alicia"))
662    }
663  }
664}
665