or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced.mdassertions.mdasync.mdfixtures.mdindex.mdmatchers.mdtest-styles.md

matchers.mddocs/

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

```