0
# Matcher System
1
2
The specs2 matcher system provides a comprehensive set of type-safe matchers for assertions in specifications. Matchers can be composed, negated, and customized to create expressive and readable test assertions.
3
4
## Core Matcher Types
5
6
### Matcher[T]
7
8
Base trait for all matchers providing composition and transformation methods.
9
10
```scala { .api }
11
trait Matcher[T] {
12
def apply[S <: T](expectable: Expectable[S]): MatchResult[S]
13
14
// Composition methods
15
def and[S <: T](m: Matcher[S]): Matcher[S]
16
def or[S <: T](m: Matcher[S]): Matcher[S]
17
def not: Matcher[T]
18
19
// Transformation methods
20
def ^^[S](f: S => T): Matcher[S]
21
def when(condition: => Boolean): Matcher[T]
22
def unless(condition: => Boolean): Matcher[T]
23
def iff(condition: => Boolean): Matcher[T]
24
25
// Utility methods
26
def eventually: Matcher[T]
27
def lazily: Matcher[T]
28
def orSkip: Matcher[T]
29
def orPending: Matcher[T]
30
}
31
```
32
33
### MatchResult[T]
34
35
Result of applying a matcher to a value.
36
37
```scala { .api }
38
case class MatchResult[T](
39
expectable: Expectable[T],
40
message: Message,
41
negatedMessage: Message
42
) {
43
def isSuccess: Boolean
44
def isFailure: Boolean
45
def message: String
46
def negatedMessage: String
47
}
48
```
49
50
### Expectable[T]
51
52
Wrapper for values being tested with additional metadata.
53
54
```scala { .api }
55
case class Expectable[T](
56
value: T,
57
description: String = "",
58
showValue: Boolean = true
59
) {
60
def must[R](m: Matcher[R]): MatchResult[T]
61
def should[R](m: Matcher[R]): MatchResult[T]
62
def aka(alias: String): Expectable[T]
63
def updateDescription(f: String => String): Expectable[T]
64
}
65
```
66
67
## Matcher Collections
68
69
### Matchers
70
71
Main trait aggregating all standard matchers.
72
73
```scala { .api }
74
trait Matchers extends AnyMatchers
75
with BeHaveMatchers
76
with TraversableMatchers
77
with MapMatchers
78
with StringMatchers
79
with NumericMatchers
80
with ExceptionMatchers
81
with OptionMatchers
82
with EitherMatchers
83
with FutureMatchers
84
with EventuallyMatchers
85
with XmlMatchers
86
with JsonMatchers
87
```
88
89
### MustMatchers
90
91
"Must" syntax for expectations that return results.
92
93
```scala { .api }
94
trait MustMatchers extends Matchers {
95
implicit def anyToMust[T](t: T): MustExpectable[T]
96
}
97
98
class MustExpectable[T](value: T) {
99
def must[R](m: Matcher[R]): MatchResult[T]
100
def must(m: Matcher[T]): MatchResult[T]
101
}
102
```
103
104
### ShouldMatchers
105
106
"Should" syntax for expectations.
107
108
```scala { .api }
109
trait ShouldMatchers extends Matchers {
110
implicit def anyToShould[T](t: T): ShouldExpectable[T]
111
}
112
113
class ShouldExpectable[T](value: T) {
114
def should[R](m: Matcher[R]): MatchResult[T]
115
def should(m: Matcher[T]): MatchResult[T]
116
}
117
```
118
119
## Core Matcher Categories
120
121
### AnyMatchers
122
123
General-purpose matchers for any type.
124
125
```scala { .api }
126
trait AnyMatchers {
127
def beTrue: Matcher[Boolean]
128
def beFalse: Matcher[Boolean]
129
def beNull[T]: Matcher[T]
130
def beEqualTo[T](t: T): Matcher[T]
131
def be_===[T](t: T): Matcher[T]
132
def beOneOf[T](values: T*): Matcher[T]
133
def beAnInstanceOf[T: ClassTag]: Matcher[Any]
134
def haveClass[T: ClassTag]: Matcher[Any]
135
def haveSuperclass[T: ClassTag]: Matcher[Any]
136
def beAssignableFrom[T: ClassTag]: Matcher[Class[_]]
137
}
138
```
139
140
**Usage Examples:**
141
```scala
142
true must beTrue
143
null must beNull
144
5 must beEqualTo(5)
145
"test" must beOneOf("test", "spec", "example")
146
List(1,2,3) must beAnInstanceOf[List[Int]]
147
```
148
149
### StringMatchers
150
151
String-specific matchers.
152
153
```scala { .api }
154
trait StringMatchers {
155
def contain(s: String): Matcher[String]
156
def startWith(s: String): Matcher[String]
157
def endWith(s: String): Matcher[String]
158
def beMatching(regex: String): Matcher[String]
159
def beMatching(regex: Regex): Matcher[String]
160
def find(regex: String): Matcher[String]
161
def have size(n: Int): Matcher[String]
162
def have length(n: Int): Matcher[String]
163
def be empty: Matcher[String]
164
def beBlank: Matcher[String]
165
}
166
```
167
168
**Usage Examples:**
169
```scala
170
"hello world" must contain("world")
171
"specs2" must startWith("spec")
172
"testing" must endWith("ing")
173
"abc123" must beMatching("\\w+\\d+")
174
"" must be empty
175
" " must beBlank
176
```
177
178
### TraversableMatchers
179
180
Matchers for collections and traversable types.
181
182
```scala { .api }
183
trait TraversableMatchers {
184
def contain[T](t: T): Matcher[Traversable[T]]
185
def containMatch[T](regex: String): Matcher[Traversable[T]]
186
def containPattern[T](regex: String): Matcher[Traversable[T]]
187
def haveSize[T](n: Int): Matcher[Traversable[T]]
188
def haveLength[T](n: Int): Matcher[Traversable[T]]
189
def be empty[T]: Matcher[Traversable[T]]
190
def beSorted[T: Ordering]: Matcher[Traversable[T]]
191
def containAllOf[T](elements: T*): Matcher[Traversable[T]]
192
def containAnyOf[T](elements: T*): Matcher[Traversable[T]]
193
def atLeastOnce[T](m: Matcher[T]): Matcher[Traversable[T]]
194
def atMostOnce[T](m: Matcher[T]): Matcher[Traversable[T]]
195
def exactly[T](n: Int, m: Matcher[T]): Matcher[Traversable[T]]
196
}
197
```
198
199
**Usage Examples:**
200
```scala
201
List(1, 2, 3) must contain(2)
202
List(1, 2, 3) must haveSize(3)
203
List.empty[Int] must be empty
204
List(1, 2, 3) must beSorted
205
List(1, 2, 3, 2) must containAllOf(1, 2)
206
List("a", "b", "c") must atLeastOnce(startWith("a"))
207
```
208
209
### NumericMatchers
210
211
Numeric comparison matchers.
212
213
```scala { .api }
214
trait NumericMatchers {
215
def beLessThan[T: Ordering](n: T): Matcher[T]
216
def beLessThanOrEqualTo[T: Ordering](n: T): Matcher[T]
217
def beGreaterThan[T: Ordering](n: T): Matcher[T]
218
def beGreaterThanOrEqualTo[T: Ordering](n: T): Matcher[T]
219
def beBetween[T: Ordering](min: T, max: T): Matcher[T]
220
def beCloseTo[T: Numeric](expected: T, delta: T): Matcher[T]
221
def bePositive[T: Numeric]: Matcher[T]
222
def beNegative[T: Numeric]: Matcher[T]
223
def beZero[T: Numeric]: Matcher[T]
224
}
225
```
226
227
**Usage Examples:**
228
```scala
229
5 must beLessThan(10)
230
10 must beGreaterThanOrEqualTo(10)
231
7 must beBetween(5, 10)
232
3.14159 must beCloseTo(3.14, 0.01)
233
5 must bePositive
234
-3 must beNegative
235
0 must beZero
236
```
237
238
### ExceptionMatchers
239
240
Matchers for testing exceptions.
241
242
```scala { .api }
243
trait ExceptionMatchers {
244
def throwA[E <: Throwable: ClassTag]: Matcher[Any]
245
def throwAn[E <: Throwable: ClassTag]: Matcher[Any]
246
def throwA[E <: Throwable: ClassTag](message: String): Matcher[Any]
247
def throwA[E <: Throwable: ClassTag](messagePattern: Regex): Matcher[Any]
248
def throwA[E <: Throwable: ClassTag](matcher: Matcher[E]): Matcher[Any]
249
}
250
```
251
252
**Usage Examples:**
253
```scala
254
{ throw new IllegalArgumentException("bad arg") } must throwAn[IllegalArgumentException]
255
{ 1 / 0 } must throwA[ArithmeticException]
256
{ validate("") } must throwA[ValidationException]("empty input")
257
{ parse("invalid") } must throwA[ParseException](beMatching(".*invalid.*"))
258
```
259
260
### OptionMatchers
261
262
Matchers for Option types.
263
264
```scala { .api }
265
trait OptionMatchers {
266
def beSome[T]: Matcher[Option[T]]
267
def beSome[T](t: T): Matcher[Option[T]]
268
def beSome[T](matcher: Matcher[T]): Matcher[Option[T]]
269
def beNone[T]: Matcher[Option[T]]
270
}
271
```
272
273
**Usage Examples:**
274
```scala
275
Some(5) must beSome
276
Some("test") must beSome("test")
277
Some(10) must beSome(beGreaterThan(5))
278
None must beNone
279
```
280
281
### EitherMatchers
282
283
Matchers for Either types.
284
285
```scala { .api }
286
trait EitherMatchers {
287
def beRight[T]: Matcher[Either[_, T]]
288
def beRight[T](t: T): Matcher[Either[_, T]]
289
def beRight[T](matcher: Matcher[T]): Matcher[Either[_, T]]
290
def beLeft[T]: Matcher[Either[T, _]]
291
def beLeft[T](t: T): Matcher[Either[T, _]]
292
def beLeft[T](matcher: Matcher[T]): Matcher[Either[T, _]]
293
}
294
```
295
296
**Usage Examples:**
297
```scala
298
Right(42) must beRight
299
Right("success") must beRight("success")
300
Left("error") must beLeft
301
Left(404) must beLeft(beGreaterThan(400))
302
```
303
304
### FutureMatchers
305
306
Matchers for asynchronous Future types.
307
308
```scala { .api }
309
trait FutureMatchers {
310
def await[T]: Matcher[Future[T]]
311
def await[T](duration: Duration): Matcher[Future[T]]
312
def beEqualTo[T](t: T): FutureMatcher[T]
313
def throwA[E <: Throwable: ClassTag]: FutureMatcher[Any]
314
}
315
316
trait FutureMatcher[T] extends Matcher[Future[T]] {
317
def await: Matcher[Future[T]]
318
def await(duration: Duration): Matcher[Future[T]]
319
}
320
```
321
322
**Usage Examples:**
323
```scala
324
Future(42) must beEqualTo(42).await
325
Future.failed(new RuntimeException) must throwA[RuntimeException].await
326
Future(slow()) must beEqualTo(result).await(5.seconds)
327
```
328
329
### EventuallyMatchers
330
331
Retry-based matchers for eventually consistent conditions.
332
333
```scala { .api }
334
trait EventuallyMatchers {
335
def eventually[T](m: Matcher[T]): Matcher[T]
336
def eventually[T](m: Matcher[T], retries: Int): Matcher[T]
337
def eventually[T](m: Matcher[T], sleep: Duration): Matcher[T]
338
def eventually[T](m: Matcher[T], retries: Int, sleep: Duration): Matcher[T]
339
def retry[T](m: Matcher[T]): Matcher[T]
340
def atMost[T](duration: Duration): RetryMatcher[T]
341
def atLeast[T](duration: Duration): RetryMatcher[T]
342
}
343
```
344
345
**Usage Examples:**
346
```scala
347
asyncOperation() must eventually(beEqualTo(expected))
348
database.count() must eventually(beGreaterThan(0), retries = 10)
349
cache.get(key) must eventually(beSome, 100.millis)
350
```
351
352
### MapMatchers
353
354
Matchers for Map types.
355
356
```scala { .api }
357
trait MapMatchers {
358
def haveKey[K](k: K): Matcher[Map[K, _]]
359
def haveValue[V](v: V): Matcher[Map[_, V]]
360
def havePair[K, V](k: K, v: V): Matcher[Map[K, V]]
361
def havePairs[K, V](pairs: (K, V)*): Matcher[Map[K, V]]
362
def haveKeys[K](keys: K*): Matcher[Map[K, _]]
363
def haveValues[V](values: V*): Matcher[Map[_, V]]
364
}
365
```
366
367
**Usage Examples:**
368
```scala
369
Map("a" -> 1, "b" -> 2) must haveKey("a")
370
Map("a" -> 1, "b" -> 2) must haveValue(1)
371
Map("a" -> 1, "b" -> 2) must havePair("a" -> 1)
372
Map("a" -> 1, "b" -> 2) must haveKeys("a", "b")
373
```
374
375
## Matcher Composition
376
377
### Logical Composition
378
379
Combine matchers with logical operators:
380
381
```scala
382
// AND composition
383
result must (beGreaterThan(0) and beLessThan(100))
384
385
// OR composition
386
result must (beEqualTo("success") or beEqualTo("ok"))
387
388
// Negation
389
result must not(beEmpty)
390
result must not(contain("error"))
391
```
392
393
### Conditional Matching
394
395
Apply matchers conditionally:
396
397
```scala
398
// When condition is true
399
result must beEqualTo(expected).when(enableValidation)
400
401
// Unless condition is true
402
result must beEmpty.unless(hasData)
403
404
// If and only if condition is true
405
result must bePositive.iff(isEnabled)
406
```
407
408
### Transformation
409
410
Transform values before matching:
411
412
```scala
413
// Transform with function
414
users must haveSize(3) ^^ (_.filter(_.active))
415
416
// Transform with partial function
417
response must beEqualTo(200) ^^ { case HttpResponse(code, _) => code }
418
```
419
420
## Custom Matchers
421
422
### Creating Custom Matchers
423
424
```scala { .api }
425
def customMatcher[T](f: T => Boolean, description: String): Matcher[T] = {
426
(t: T) => {
427
val result = f(t)
428
MatchResult(result, s"$t $description", s"$t does not $description")
429
}
430
}
431
```
432
433
**Examples:**
434
```scala
435
def beValidEmail = beMatching("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b".r) ^^
436
((_: String).toLowerCase, "a valid email address")
437
438
def haveValidChecksum = customMatcher[File](
439
file => calculateChecksum(file) == expectedChecksum,
440
"have valid checksum"
441
)
442
443
"user@example.com" must beValidEmail
444
new File("data.txt") must haveValidChecksum
445
```
446
447
### Matcher Combinators
448
449
Build complex matchers from simple ones:
450
451
```scala
452
def beValidUser = (
453
have(name = not(beEmpty)) and
454
have(email = beValidEmail) and
455
have(age = beGreaterThan(0))
456
)
457
458
User("john", "john@test.com", 25) must beValidUser
459
```
460
461
## Advanced Features
462
463
### BeHaveMatchers
464
465
Natural language matchers using "be" and "have":
466
467
```scala { .api }
468
trait BeHaveMatchers {
469
def be(m: Matcher[Any]): Matcher[Any]
470
def have(m: Matcher[Any]): Matcher[Any]
471
}
472
```
473
474
**Usage:**
475
```scala
476
user must be(valid)
477
list must have(size(3))
478
file must be(readable)
479
response must have(status(200))
480
```
481
482
### Scope Matchers
483
484
Test values within specific scopes:
485
486
```scala
487
users must contain { user: User =>
488
user.name must startWith("John")
489
user.age must beGreaterThan(18)
490
}
491
```
492
493
### Message Customization
494
495
Customize matcher failure messages:
496
497
```scala
498
def bePositive = be_>=(0) ^^ ((_: Int), "a positive number")
499
500
(-5) must bePositive
501
// Failure: -5 is not a positive number
502
```
503
504
## Best Practices
505
506
1. **Use descriptive matchers**: Choose matchers that clearly express intent
507
2. **Compose logically**: Use `and`/`or` for complex conditions
508
3. **Custom matchers for domain logic**: Create domain-specific matchers for better readability
509
4. **Handle async properly**: Use `eventually` and `await` for asynchronous operations
510
5. **Meaningful failure messages**: Customize messages for better debugging
511
6. **Type safety**: Leverage Scala's type system for compile-time safety
512
7. **Performance considerations**: Be mindful of expensive operations in matcher composition