or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

asymmetric-matchers.mdcollection-string-matchers.mdconfiguration-extension.mdcore-matchers.mdexception-matchers.mdindex.mdnumeric-matchers.mdspy-mock-matchers.md
tile.json

configuration-extension.mddocs/

0

# Configuration and Extension

1

2

Methods for extending Jest Matchers with custom matchers and configuring test behavior. These utilities allow for customization of the testing framework and assertion counting.

3

4

## Capabilities

5

6

### Custom Matcher Extension

7

8

#### expect.extend

9

10

Adds custom matchers to the expect function, allowing you to create domain-specific assertions.

11

12

```javascript { .api }

13

/**

14

* Adds custom matchers to the expect function

15

* @param matchers - Object containing custom matcher functions

16

*/

17

expect.extend(matchers: MatchersObject): void;

18

19

interface MatchersObject {

20

[matcherName: string]: RawMatcherFn;

21

}

22

23

interface RawMatcherFn {

24

(this: MatcherState, received: any, ...args: any[]): ExpectationResult;

25

}

26

27

interface ExpectationResult {

28

pass: boolean;

29

message?: string | (() => string);

30

}

31

32

interface MatcherState {

33

isNot: boolean;

34

utils: MatcherUtils;

35

equals: (a: any, b: any) => boolean;

36

dontThrow: () => void;

37

}

38

```

39

40

**Usage Examples:**

41

42

```javascript

43

// Define custom matchers

44

expect.extend({

45

toBeEven(received) {

46

const pass = received % 2 === 0;

47

return {

48

pass,

49

message: () =>

50

`expected ${received} ${pass ? 'not ' : ''}to be even`

51

};

52

},

53

54

toBeWithinRange(received, floor, ceiling) {

55

const pass = received >= floor && received <= ceiling;

56

return {

57

pass,

58

message: () =>

59

`expected ${received} ${pass ? 'not ' : ''}to be within range ${floor} - ${ceiling}`

60

};

61

},

62

63

toHaveValidEmail(received) {

64

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

65

const pass = typeof received === 'string' && emailRegex.test(received);

66

67

return {

68

pass,

69

message: () =>

70

`expected ${received} ${pass ? 'not ' : ''}to be a valid email address`

71

};

72

}

73

});

74

75

// Use custom matchers

76

expect(4).toBeEven();

77

expect(3).not.toBeEven();

78

79

expect(5).toBeWithinRange(1, 10);

80

expect(15).not.toBeWithinRange(1, 10);

81

82

expect('user@example.com').toHaveValidEmail();

83

expect('invalid-email').not.toHaveValidEmail();

84

```

85

86

**Advanced Custom Matcher Examples:**

87

88

```javascript

89

expect.extend({

90

// Async matcher

91

async toEventuallyEqual(received, expected, timeout = 1000) {

92

const startTime = Date.now();

93

94

while (Date.now() - startTime < timeout) {

95

if (this.equals(received(), expected)) {

96

return {

97

pass: true,

98

message: () => `expected function not to eventually return ${expected}`

99

};

100

}

101

await new Promise(resolve => setTimeout(resolve, 10));

102

}

103

104

return {

105

pass: false,

106

message: () => `expected function to eventually return ${expected} within ${timeout}ms`

107

};

108

},

109

110

// Matcher with detailed diff

111

toMatchUserSchema(received) {

112

const requiredFields = ['id', 'name', 'email'];

113

const missingFields = requiredFields.filter(field => !(field in received));

114

const pass = missingFields.length === 0;

115

116

return {

117

pass,

118

message: () => {

119

if (pass) {

120

return `expected user object not to match schema`;

121

} else {

122

return `expected user object to match schema, missing fields: ${missingFields.join(', ')}`;

123

}

124

}

125

};

126

},

127

128

// Matcher using this.utils for formatting

129

toHaveProperty(received, property, expectedValue) {

130

const hasProperty = Object.prototype.hasOwnProperty.call(received, property);

131

const actualValue = received[property];

132

133

if (arguments.length === 2) {

134

// Only check property existence

135

return {

136

pass: hasProperty,

137

message: () =>

138

`expected object ${hasProperty ? 'not ' : ''}to have property '${property}'`

139

};

140

} else {

141

// Check property existence and value

142

const pass = hasProperty && this.equals(actualValue, expectedValue);

143

return {

144

pass,

145

message: () => {

146

if (!hasProperty) {

147

return `expected object to have property '${property}'`;

148

} else if (!this.equals(actualValue, expectedValue)) {

149

return `expected property '${property}' to be ${this.utils.printExpected(expectedValue)}, received ${this.utils.printReceived(actualValue)}`;

150

} else {

151

return `expected property '${property}' not to be ${this.utils.printExpected(expectedValue)}`;

152

}

153

}

154

};

155

}

156

}

157

});

158

159

// Usage

160

await expect(() => getCurrentValue()).toEventuallyEqual(42, 2000);

161

162

expect({id: 1, name: 'John', email: 'john@example.com'}).toMatchUserSchema();

163

164

expect(user).toHaveProperty('name', 'John');

165

```

166

167

### Assertion Counting

168

169

#### expect.assertions

170

171

Sets the expected number of assertions in a test. Useful for ensuring async code paths are tested.

172

173

```javascript { .api }

174

/**

175

* Sets expected number of assertions in a test

176

* @param expected - Expected number of assertions to be called

177

*/

178

expect.assertions(expected: number): void;

179

```

180

181

**Usage Examples:**

182

183

```javascript

184

// Ensure async callbacks are called

185

test('async test with assertion counting', async () => {

186

expect.assertions(2);

187

188

const callback = jest.fn((result) => {

189

expect(result).toBeDefined();

190

expect(result.success).toBe(true);

191

});

192

193

await processDataAsync(data, callback);

194

});

195

196

// Testing conditional code paths

197

test('conditional assertions', () => {

198

expect.assertions(1);

199

200

const value = getValue();

201

202

if (value > 0) {

203

expect(value).toBeGreaterThan(0);

204

} else {

205

expect(value).toBeLessThanOrEqual(0);

206

}

207

});

208

209

// Testing promise rejections

210

test('promise rejection', async () => {

211

expect.assertions(1);

212

213

try {

214

await riskyOperation();

215

} catch (error) {

216

expect(error.message).toContain('operation failed');

217

}

218

});

219

```

220

221

#### expect.hasAssertions

222

223

Verifies that at least one assertion is called during the test. More flexible than `expect.assertions()`.

224

225

```javascript { .api }

226

/**

227

* Verifies at least one assertion is called

228

*/

229

expect.hasAssertions(): void;

230

```

231

232

**Usage Examples:**

233

234

```javascript

235

// Ensure some assertion is made in complex async flow

236

test('complex async flow', async () => {

237

expect.hasAssertions();

238

239

const results = await Promise.all([

240

processItem(item1),

241

processItem(item2),

242

processItem(item3)

243

]);

244

245

results.forEach(result => {

246

if (result.shouldValidate) {

247

expect(result.data).toBeDefined();

248

}

249

});

250

});

251

252

// Testing event handlers

253

test('event handler', () => {

254

expect.hasAssertions();

255

256

const handler = (event) => {

257

if (event.type === 'error') {

258

expect(event.message).toBeDefined();

259

} else if (event.type === 'success') {

260

expect(event.data).toBeDefined();

261

}

262

};

263

264

emitter.emit('success', {data: 'test'});

265

});

266

```

267

268

### State Management

269

270

#### expect.getState

271

272

Gets the current global matcher state, including assertion counts and custom state.

273

274

```javascript { .api }

275

/**

276

* Gets current global matcher state

277

* @returns Current matcher state object

278

*/

279

expect.getState(): Object;

280

```

281

282

**Usage Examples:**

283

284

```javascript

285

// Check current assertion count

286

const state = expect.getState();

287

console.log(`Assertions so far: ${state.assertionCalls}`);

288

289

// Access custom state in matchers

290

expect.extend({

291

toTrackCalls(received) {

292

const state = expect.getState();

293

console.log(`This is assertion #${state.assertionCalls + 1}`);

294

295

return {

296

pass: true,

297

message: () => 'tracking call'

298

};

299

}

300

});

301

```

302

303

#### expect.setState

304

305

Sets or updates the global matcher state. Useful for sharing data between custom matchers.

306

307

```javascript { .api }

308

/**

309

* Sets global matcher state

310

* @param state - State object to merge with current state

311

*/

312

expect.setState(state: Object): void;

313

```

314

315

**Usage Examples:**

316

317

```javascript

318

// Set up shared state for custom matchers

319

expect.setState({

320

testStartTime: Date.now(),

321

customConfig: {

322

strictMode: true,

323

debugMode: false

324

}

325

});

326

327

// Custom matcher using shared state

328

expect.extend({

329

toCompleteWithinTime(received, maxDuration) {

330

const { testStartTime } = expect.getState();

331

const actualDuration = Date.now() - testStartTime;

332

const pass = actualDuration <= maxDuration;

333

334

return {

335

pass,

336

message: () =>

337

`expected test to complete within ${maxDuration}ms, took ${actualDuration}ms`

338

};

339

},

340

341

toRespectStrictMode(received) {

342

const { customConfig } = expect.getState();

343

344

if (!customConfig?.strictMode) {

345

return { pass: true, message: () => 'strict mode disabled' };

346

}

347

348

// Perform strict validation

349

const pass = validateStrict(received);

350

return {

351

pass,

352

message: () => `expected ${received} to pass strict validation`

353

};

354

}

355

});

356

357

// Usage

358

expect(result).toCompleteWithinTime(1000);

359

expect(data).toRespectStrictMode();

360

```

361

362

### Snapshot Serializer Support

363

364

#### expect.addSnapshotSerializer

365

366

No-op function for compatibility with Jest's snapshot serializer system.

367

368

```javascript { .api }

369

/**

370

* No-op function for snapshot serializer compatibility

371

*/

372

expect.addSnapshotSerializer(): void;

373

```

374

375

**Usage Examples:**

376

377

```javascript

378

// This is a compatibility function - it does nothing in jest-matchers

379

expect.addSnapshotSerializer();

380

381

// In full Jest environment, you would use it like:

382

// expect.addSnapshotSerializer({

383

// serialize: (val) => val.toString(),

384

// test: (val) => val instanceof MyClass

385

// });

386

```

387

388

## Advanced Configuration Patterns

389

390

### Setting Up Custom Matcher Suites

391

392

```javascript

393

// Create reusable matcher suites

394

const customMatchers = {

395

// Date matchers

396

toBeToday(received) {

397

const today = new Date().toDateString();

398

const receivedDate = new Date(received).toDateString();

399

const pass = today === receivedDate;

400

401

return {

402

pass,

403

message: () => `expected ${received} ${pass ? 'not ' : ''}to be today`

404

};

405

},

406

407

// HTTP status matchers

408

toBeSuccessStatus(received) {

409

const pass = received >= 200 && received < 300;

410

return {

411

pass,

412

message: () => `expected status ${received} ${pass ? 'not ' : ''}to be successful (2xx)`

413

};

414

},

415

416

// Object structure matchers

417

toHaveRequiredFields(received, requiredFields) {

418

const missingFields = requiredFields.filter(field => !(field in received));

419

const pass = missingFields.length === 0;

420

421

return {

422

pass,

423

message: () => {

424

if (pass) {

425

return `expected object not to have all required fields`;

426

} else {

427

return `expected object to have required fields: ${missingFields.join(', ')} missing`;

428

}

429

}

430

};

431

}

432

};

433

434

// Apply all custom matchers

435

expect.extend(customMatchers);

436

437

// Usage

438

expect(new Date()).toBeToday();

439

expect(200).toBeSuccessStatus();

440

expect(user).toHaveRequiredFields(['id', 'name', 'email']);

441

```

442

443

### Global Test Configuration

444

445

```javascript

446

// Set up global test state

447

beforeEach(() => {

448

expect.setState({

449

testStartTime: Date.now(),

450

testId: Math.random().toString(36),

451

config: {

452

timeout: 5000,

453

retries: 3

454

}

455

});

456

});

457

458

// Custom matchers that use global config

459

expect.extend({

460

toCompleteWithinTimeout(received) {

461

const { config, testStartTime } = expect.getState();

462

const duration = Date.now() - testStartTime;

463

const pass = duration <= config.timeout;

464

465

return {

466

pass,

467

message: () =>

468

`expected operation to complete within ${config.timeout}ms, took ${duration}ms`

469

};

470

}

471

});

472

```