0
# Matchers
1
2
ScalaTest's matcher framework provides an expressive DSL for writing readable and maintainable test assertions. The framework offers two main DSL styles - "should" and "must" - with extensive built-in matchers covering equality, numeric comparisons, collections, strings, types, exceptions, and more.
3
4
## Capabilities
5
6
### Core Matcher Framework
7
8
The foundation of the matcher system with base traits and result types.
9
10
```scala { .api }
11
/**
12
* Base trait for all matchers
13
*/
14
trait Matcher[-T] {
15
/**
16
* Apply the matcher to a value
17
* @param left the value to match against
18
* @return MatchResult indicating success/failure with messages
19
*/
20
def apply(left: T): MatchResult
21
22
/**
23
* Compose this matcher with a transformation function
24
* @param f function to transform input before matching
25
* @return new matcher that applies transformation first
26
*/
27
def compose[U](f: U => T): Matcher[U]
28
}
29
30
/**
31
* Result of applying a matcher
32
*/
33
case class MatchResult(
34
matches: Boolean, // Whether the match succeeded
35
failureMessage: String, // Message displayed on failure
36
negatedFailureMessage: String, // Message displayed when negated match fails
37
midSentenceFailureMessage: String = "", // Message for mid-sentence contexts
38
midSentenceNegatedFailureMessage: String = "" // Negated mid-sentence message
39
)
40
41
/**
42
* Matcher for use with "be" syntax
43
*/
44
trait BeMatcher[-T] {
45
def apply(left: T): MatchResult
46
}
47
48
/**
49
* Matcher for object properties with "be" syntax
50
*/
51
trait BePropertyMatcher[-T] {
52
def apply(objectWithProperty: T): BePropertyMatchResult
53
}
54
55
/**
56
* Matcher for object properties with "have" syntax
57
*/
58
trait HavePropertyMatcher[-T, +P] {
59
def apply(objectWithProperty: T): HavePropertyMatchResult[P]
60
}
61
```
62
63
### Should Matchers DSL
64
65
The primary matcher DSL using "should" syntax for natural, readable assertions.
66
67
```scala { .api }
68
/**
69
* Complete "should" matcher DSL - mix into test suites for matcher functionality
70
*/
71
trait Matchers extends ShouldVerb with MatcherWords with Tolerance {
72
// Enables: value should matcher
73
// All matcher methods and implicit conversions are available
74
}
75
76
/**
77
* Core "should" verb that enables matcher syntax
78
*/
79
trait ShouldVerb {
80
implicit def convertToAnyShouldWrapper[T](o: T): AnyShouldWrapper[T]
81
82
final class AnyShouldWrapper[T](val leftSideValue: T) {
83
def should(rightMatcherX1: Matcher[T]): Assertion
84
def should(notWord: NotWord): ResultOfNotWordForAny[T]
85
def shouldNot(rightMatcherX1: Matcher[T]): Assertion
86
}
87
}
88
```
89
90
### Equality Matchers
91
92
Test value equality with various comparison strategies.
93
94
```scala { .api }
95
/**
96
* Test exact equality
97
* @param right expected value
98
*/
99
def equal[T](right: T): Matcher[T]
100
101
/**
102
* Symbolic equality matcher (same as equal)
103
*/
104
def ===[T](right: T): Matcher[T]
105
106
/**
107
* Reference equality matcher
108
*/
109
def be[T <: AnyRef](right: T): Matcher[T]
110
111
/**
112
* "be" with tolerance for floating point comparison
113
*/
114
def be(right: Double): Matcher[Double]
115
def be(right: Float): Matcher[Float]
116
```
117
118
**Usage Examples:**
119
120
```scala
121
import org.scalatest.funsuite.AnyFunSuite
122
import org.scalatest.matchers.should.Matchers
123
124
class EqualityMatcherSpec extends AnyFunSuite with Matchers {
125
126
test("equality matchers") {
127
val x = 42
128
val y = 42
129
val list1 = List(1, 2, 3)
130
val list2 = List(1, 2, 3)
131
132
// Value equality
133
x should equal(42)
134
x should ===(y)
135
list1 should equal(list2)
136
137
// Reference equality (for AnyRef)
138
val str1 = new String("hello")
139
val str2 = new String("hello")
140
str1 should equal(str2) // true - value equality
141
str1 should not be str2 // true - different references
142
143
// Negation
144
x should not equal 43
145
x shouldNot equal(43)
146
}
147
148
test("floating point equality with tolerance") {
149
val result = 0.1 + 0.2
150
151
// Direct equality often fails due to floating point precision
152
// result should equal(0.3) // might fail
153
154
// Use tolerance for floating point comparison
155
result should equal(0.3 +- 0.001)
156
result should be(0.3 +- 0.001)
157
}
158
}
159
```
160
161
### Numeric Matchers
162
163
Compare numeric values with relational operators.
164
165
```scala { .api }
166
/**
167
* Numeric comparison matchers
168
*/
169
def be > [T](right: T)(implicit ord: Ordering[T]): Matcher[T]
170
def be >= [T](right: T)(implicit ord: Ordering[T]): Matcher[T]
171
def be < [T](right: T)(implicit ord: Ordering[T]): Matcher[T]
172
def be <= [T](right: T)(implicit ord: Ordering[T]): Matcher[T]
173
174
/**
175
* Tolerance matcher for floating point comparison
176
*/
177
def +-(tolerance: Double): Spread[Double]
178
def +-(tolerance: Float): Spread[Float]
179
```
180
181
**Usage Examples:**
182
183
```scala
184
test("numeric matchers") {
185
val score = 85
186
val price = 29.99
187
val count = 0
188
189
// Relational comparisons
190
score should be > 80
191
score should be >= 85
192
score should be < 100
193
score should be <= 85
194
195
// Works with any ordered type
196
price should be > 20.0
197
count should be >= 0
198
199
// Tolerance comparison
200
val calculation = 10.0 / 3.0
201
calculation should equal(3.333 +- 0.01)
202
calculation should be(3.333 +- 0.001)
203
}
204
```
205
206
### String Matchers
207
208
Specialized matchers for string content and patterns.
209
210
```scala { .api }
211
/**
212
* String content matchers
213
*/
214
def startWith(right: String): Matcher[String]
215
def endWith(right: String): Matcher[String]
216
def include(right: String): Matcher[String]
217
218
/**
219
* Regular expression matchers
220
*/
221
def fullyMatch(right: Regex): Matcher[String]
222
def include regex(right: Regex): Matcher[String]
223
def startWith regex(right: Regex): Matcher[String]
224
def endWith regex(right: Regex): Matcher[String]
225
226
/**
227
* Length matcher for strings and collections
228
*/
229
def have length(expectedLength: Long): HavePropertyMatcher[AnyRef, Long]
230
def have size(expectedSize: Long): HavePropertyMatcher[AnyRef, Long]
231
```
232
233
**Usage Examples:**
234
235
```scala
236
test("string matchers") {
237
val message = "Hello, World!"
238
val email = "user@example.com"
239
val code = "ABC123"
240
241
// Content matching
242
message should startWith("Hello")
243
message should endWith("World!")
244
message should include("lo, Wo")
245
246
// Case sensitivity
247
message should startWith("hello") // fails
248
message should startWith("Hello") // succeeds
249
250
// Email validation with regex
251
val emailPattern = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""".r
252
email should fullyMatch(emailPattern)
253
254
// Partial regex matching
255
code should include regex "[0-9]+".r
256
code should startWith regex "[A-Z]+".r
257
258
// Length checking
259
message should have length 13
260
email should have size 16
261
}
262
```
263
264
### Collection Matchers
265
266
Comprehensive matchers for collections, sequences, and iterables.
267
268
```scala { .api }
269
/**
270
* Collection content matchers
271
*/
272
def contain[T](right: T): Matcher[GenTraversable[T]]
273
def contain oneOf[T](firstEle: T, secondEle: T, remainingEles: T*): Matcher[GenTraversable[T]]
274
def contain allOf[T](firstEle: T, secondEle: T, remainingEles: T*): Matcher[GenTraversable[T]]
275
def contain noneOf[T](firstEle: T, secondEle: T, remainingEles: T*): Matcher[GenTraversable[T]]
276
def contain only[T](right: T*): Matcher[GenTraversable[T]]
277
def contain inOrderOnly[T](firstEle: T, secondEle: T, remainingEles: T*): Matcher[GenTraversable[T]]
278
def contain theSameElementsAs[T](right: GenTraversable[T]): Matcher[GenTraversable[T]]
279
def contain theSameElementsInOrderAs[T](right: GenTraversable[T]): Matcher[GenTraversable[T]]
280
281
/**
282
* Collection property matchers
283
*/
284
def be(empty: EmptyWord): Matcher[AnyRef with java.util.Collection[_]]
285
def have size[T](expectedSize: Long): HavePropertyMatcher[scala.collection.GenTraversable[T], Long]
286
def have length[T](expectedLength: Long): HavePropertyMatcher[AnyRef, Long]
287
288
/**
289
* Sequence-specific matchers
290
*/
291
def be(sorted: SortedWord): Matcher[scala.collection.GenSeq[T]]
292
```
293
294
**Usage Examples:**
295
296
```scala
297
test("collection matchers") {
298
val numbers = List(1, 2, 3, 4, 5)
299
val names = Set("Alice", "Bob", "Charlie")
300
val empty = List.empty[String]
301
val duplicates = List(1, 2, 2, 3)
302
303
// Element membership
304
numbers should contain(3)
305
numbers should contain oneOf(3, 6, 9)
306
numbers should contain allOf(1, 3, 5)
307
numbers should contain noneOf(6, 7, 8)
308
309
// Exact content matching
310
numbers should contain only(1, 2, 3, 4, 5)
311
numbers should contain theSameElementsAs(List(5, 4, 3, 2, 1))
312
313
// Order-sensitive matching
314
numbers should contain inOrderOnly(1, 2, 3, 4, 5)
315
numbers should contain theSameElementsInOrderAs(List(1, 2, 3, 4, 5))
316
317
// Collection properties
318
empty should be(empty)
319
numbers should have size 5
320
numbers should have length 5
321
numbers should be(sorted)
322
duplicates shouldNot be(sorted)
323
324
// Set operations
325
names should contain("Alice")
326
names should have size 3
327
}
328
```
329
330
### Type and Class Matchers
331
332
Test object types, class membership, and inheritance relationships.
333
334
```scala { .api }
335
/**
336
* Type testing matchers
337
*/
338
def a[T: ClassTag]: AMatcher[Any]
339
def an[T: ClassTag]: AnMatcher[Any]
340
341
/**
342
* Class testing matchers
343
*/
344
def be(aType: ResultOfATypeInvocation[_]): Matcher[Any]
345
def be(anType: ResultOfAnTypeInvocation[_]): Matcher[Any]
346
```
347
348
**Usage Examples:**
349
350
```scala
351
test("type and class matchers") {
352
val obj: Any = "hello"
353
val num: Any = 42
354
val list: Any = List(1, 2, 3)
355
356
// Type checking with articles
357
obj should be a 'string // deprecated syntax
358
obj should be a String
359
obj should be an instanceOf[String]
360
361
num should be an Integer
362
num should be an instanceOf[Integer]
363
364
list should be a List[_]
365
list should be an instanceOf[List[_]]
366
367
// Class checking
368
obj shouldNot be an Integer
369
num shouldNot be a String
370
}
371
```
372
373
### Exception Matchers
374
375
Test that code throws expected exceptions with optional message checking.
376
377
```scala { .api }
378
/**
379
* Exception testing matchers
380
*/
381
def thrownBy(codeBlock: => Any): ResultOfThrownByInvocation
382
def a[T <: AnyRef]: ResultOfATypeInvocation[T]
383
def an[T <: AnyRef]: ResultOfAnTypeInvocation[T]
384
385
// Usage: an[ExceptionType] should be thrownBy { code }
386
```
387
388
**Usage Examples:**
389
390
```scala
391
test("exception matchers") {
392
// Test that specific exception type is thrown
393
an[IllegalArgumentException] should be thrownBy {
394
require(false, "This should fail")
395
}
396
397
a[RuntimeException] should be thrownBy {
398
throw new RuntimeException("Something went wrong")
399
}
400
401
// Test exception message content
402
val exception = the[ValidationException] thrownBy {
403
validateUser(invalidUser)
404
}
405
exception.getMessage should include("email")
406
exception.getFieldName should equal("email")
407
408
// Test that no exception is thrown
409
noException should be thrownBy {
410
safeOperation()
411
}
412
}
413
```
414
415
### Boolean and Property Matchers
416
417
Test boolean values and object properties.
418
419
```scala { .api }
420
/**
421
* Boolean value matchers
422
*/
423
def be(true: TrueWord): Matcher[Boolean]
424
def be(false: FalseWord): Matcher[Boolean]
425
426
/**
427
* Property existence matchers
428
*/
429
def be(defined: DefinedWord): Matcher[Option[_]]
430
def be(empty: EmptyWord): Matcher[AnyRef]
431
def be(readable: ReadableWord): Matcher[java.io.File]
432
def be(writable: WritableWord): Matcher[java.io.File]
433
def be(sorted: SortedWord): Matcher[scala.collection.GenSeq[_]]
434
```
435
436
**Usage Examples:**
437
438
```scala
439
test("boolean and property matchers") {
440
val isValid = true
441
val result: Option[String] = Some("value")
442
val emptyResult: Option[String] = None
443
val numbers = List(1, 2, 3, 4)
444
val scrambled = List(3, 1, 4, 2)
445
446
// Boolean testing
447
isValid should be(true)
448
!isValid should be(false)
449
450
// Option testing
451
result should be(defined)
452
emptyResult shouldNot be(defined)
453
454
// Collection properties
455
List.empty should be(empty)
456
numbers shouldNot be(empty)
457
numbers should be(sorted)
458
scrambled shouldNot be(sorted)
459
}
460
```
461
462
### File and IO Matchers
463
464
Test file system properties and IO operations.
465
466
```scala { .api }
467
/**
468
* File property matchers
469
*/
470
def exist: Matcher[java.io.File]
471
def be(readable: ReadableWord): Matcher[java.io.File]
472
def be(writable: WritableWord): Matcher[java.io.File]
473
def be(executable: ExecutableWord): Matcher[java.io.File]
474
```
475
476
**Usage Examples:**
477
478
```scala
479
import java.io.File
480
481
test("file matchers") {
482
val configFile = new File("config.properties")
483
val tempFile = new File("/tmp/test.txt")
484
val scriptFile = new File("deploy.sh")
485
486
// File existence
487
configFile should exist
488
new File("nonexistent.txt") shouldNot exist
489
490
// File permissions (platform dependent)
491
configFile should be(readable)
492
tempFile should be(writable)
493
scriptFile should be(executable)
494
}
495
```
496
497
### Must Matchers DSL
498
499
Alternative DSL using "must" instead of "should" - identical functionality with different syntax.
500
501
```scala { .api }
502
/**
503
* "Must" matcher DSL - alternative to "should" syntax
504
*/
505
trait MustMatchers extends MustVerb with MatcherWords with Tolerance {
506
// Enables: value must matcher
507
// All same matchers available as "should" DSL
508
}
509
510
implicit def convertToAnyMustWrapper[T](o: T): AnyMustWrapper[T]
511
512
final class AnyMustWrapper[T](val leftSideValue: T) {
513
def must(rightMatcherX1: Matcher[T]): Assertion
514
def must(notWord: NotWord): ResultOfNotWordForAny[T]
515
def mustNot(rightMatcherX1: Matcher[T]): Assertion
516
}
517
```
518
519
**Usage Example:**
520
521
```scala
522
import org.scalatest.funsuite.AnyFunSuite
523
import org.scalatest.matchers.must.Matchers
524
525
class MustMatcherSpec extends AnyFunSuite with Matchers {
526
527
test("must syntax examples") {
528
val value = 42
529
val text = "Hello World"
530
val items = List(1, 2, 3)
531
532
// Same matchers, different syntax
533
value must equal(42)
534
value must be > 40
535
value mustNot equal(43)
536
537
text must startWith("Hello")
538
text must include("World")
539
text must have length 11
540
541
items must contain(2)
542
items must have size 3
543
items must be(sorted)
544
}
545
}
546
```
547
548
### Custom Matchers
549
550
Create domain-specific matchers for reusable, expressive test assertions.
551
552
```scala { .api }
553
/**
554
* Base for creating custom matchers
555
*/
556
trait Matcher[-T] {
557
def apply(left: T): MatchResult
558
}
559
560
/**
561
* Helper for creating simple matchers
562
*/
563
def Matcher[T](fun: T => MatchResult): Matcher[T]
564
```
565
566
**Usage Examples:**
567
568
```scala
569
import org.scalatest.matchers.{MatchResult, Matcher}
570
571
class CustomMatcherSpec extends AnyFunSuite with Matchers {
572
573
// Custom matcher for even numbers
574
def beEven: Matcher[Int] = Matcher { (left: Int) =>
575
MatchResult(
576
left % 2 == 0,
577
s"$left was not even",
578
s"$left was even"
579
)
580
}
581
582
// Custom matcher for valid email addresses
583
def beValidEmail: Matcher[String] = Matcher { (left: String) =>
584
val isValid = left.contains("@") && left.contains(".") &&
585
!left.startsWith("@") && !left.endsWith("@")
586
MatchResult(
587
isValid,
588
s"'$left' was not a valid email address",
589
s"'$left' was a valid email address"
590
)
591
}
592
593
// Custom matcher with parameters
594
def haveWordsCount(expectedCount: Int): Matcher[String] = Matcher { (left: String) =>
595
val actualCount = left.split("\\s+").length
596
MatchResult(
597
actualCount == expectedCount,
598
s"'$left' had $actualCount words instead of $expectedCount",
599
s"'$left' had $expectedCount words"
600
)
601
}
602
603
test("custom matchers in action") {
604
// Using custom matchers
605
4 should beEven
606
3 shouldNot beEven
607
608
"user@example.com" should beValidEmail
609
"invalid-email" shouldNot beValidEmail
610
611
"Hello beautiful world" should haveWordsCount(3)
612
"Single" should haveWordsCount(1)
613
}
614
}
615
```
616
617
### Matcher Composition
618
619
Combine and transform matchers for complex assertions.
620
621
**Usage Examples:**
622
623
```scala
624
test("matcher composition") {
625
val users = List(
626
User("Alice", 25, "alice@example.com"),
627
User("Bob", 30, "bob@example.com"),
628
User("Charlie", 35, "charlie@example.com")
629
)
630
631
// Compose matchers with transformations
632
users should contain(beValidEmail compose (_.email))
633
users should contain(be > 25 compose (_.age))
634
635
// Multiple composition
636
val activeAdults = beTrue compose ((u: User) => u.age >= 18 && u.isActive)
637
users should contain(activeAdults)
638
}
639
```
640
641
## Common Patterns
642
643
### Combining Multiple Matchers
644
645
```scala
646
// Multiple assertions on the same value
647
val user = getUser(123)
648
user.name should (startWith("John") and endWith("Doe"))
649
user.age should (be >= 18 and be <= 100)
650
user.email should (include("@") and endWith(".com"))
651
```
652
653
### Tolerant Matching
654
655
```scala
656
// Floating point with tolerance
657
val result = complexCalculation()
658
result should equal(3.14159 +- 0.001)
659
660
// String case-insensitive matching
661
text.toLowerCase should equal(expected.toLowerCase)
662
```
663
664
### Collection Testing Patterns
665
666
```scala
667
// Testing collection transformations
668
numbers.map(_ * 2) should contain theSameElementsAs List(2, 4, 6, 8)
669
670
// Testing filtering
671
users.filter(_.isActive) should have size 3
672
users.filter(_.age > 25) should contain only (alice, bob)
673
```