or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mdbenchmarking.mdbrowser-testing.mdconfiguration.mdindex.mdmocking.mdnode-apis.mdreporters.mdtest-definition.mdtimers.mdtype-testing.md

test-definition.mddocs/

0

# Test Definition and Lifecycle

1

2

API for defining tests, organizing them into suites, and managing test lifecycle with hooks.

3

4

## Test Definition

5

6

```typescript { .api }

7

function test(name: string, fn: TestFunction, options?: TestOptions): void;

8

function it(name: string, fn: TestFunction, options?: TestOptions): void; // Alias

9

10

interface TestFunction {

11

(context: TestContext): void | Promise<void>;

12

}

13

14

interface TestOptions {

15

timeout?: number; // Test timeout in ms

16

retry?: number; // Retries on failure

17

concurrent?: boolean; // Run concurrently

18

repeats?: number; // Repeat test N times

19

}

20

21

interface TestContext {

22

meta: Record<string, any>; // Metadata storage

23

task: RunnerTestCase; // Test task object

24

expect: ExpectStatic; // Expect function

25

}

26

```

27

28

**Example:**

29

```typescript

30

test('basic test', () => {

31

expect(1 + 2).toBe(3);

32

});

33

34

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

35

const data = await fetchData();

36

expect(data).toBeDefined();

37

});

38

39

test('with timeout', async () => {

40

await longOperation();

41

}, { timeout: 10000 });

42

43

test('with retry', () => {

44

// Flaky test code

45

}, { retry: 3 });

46

```

47

48

## Suite Definition

49

50

```typescript { .api }

51

function describe(name: string, fn: () => void, options?: TestOptions): void;

52

function suite(name: string, fn: () => void, options?: TestOptions): void; // Alias

53

```

54

55

**Example:**

56

```typescript

57

describe('Math operations', () => {

58

test('addition', () => expect(1 + 2).toBe(3));

59

test('subtraction', () => expect(5 - 2).toBe(3));

60

61

describe('multiplication', () => {

62

test('positive', () => expect(2 * 3).toBe(6));

63

test('negative', () => expect(-2 * -3).toBe(6));

64

});

65

});

66

```

67

68

## Test Modifiers

69

70

| Modifier | Purpose | Example |

71

|----------|---------|---------|

72

| `.only` | Run only this test/suite | `test.only('runs', ...)` |

73

| `.skip` | Skip this test/suite | `test.skip('skipped', ...)` |

74

| `.todo` | Mark as not implemented | `test.todo('implement later')` |

75

| `.skipIf(cond)` | Skip if condition true | `test.skipIf(isWindows)` |

76

| `.runIf(cond)` | Run only if condition true | `test.runIf(hasEnv)` |

77

| `.concurrent` | Run concurrently | `test.concurrent('parallel', ...)` |

78

| `.sequential` | Run sequentially | `test.sequential('serial', ...)` |

79

| `.fails` | Expect test to fail | `test.fails('expected fail', ...)` |

80

| `.each(data)` | Parameterized test | `test.each([...])('test %i', ...)` |

81

82

**Examples:**

83

```typescript

84

// Run only specific tests

85

test.only('focused test', () => { /* runs */ });

86

test('other test', () => { /* skipped */ });

87

88

// Conditional execution

89

test.skipIf(process.platform === 'win32')('Unix only', () => { ... });

90

test.runIf(process.env.INTEGRATION)('integration test', () => { ... });

91

92

// Concurrent execution

93

describe.concurrent('parallel suite', () => {

94

test('test 1', async () => { /* runs in parallel */ });

95

test('test 2', async () => { /* runs in parallel */ });

96

});

97

98

// Expected failures

99

test.fails('expected to fail', () => {

100

expect(1).toBe(2); // Test passes because it fails as expected

101

});

102

103

// Parameterized tests

104

test.each([

105

[1, 1, 2],

106

[2, 2, 4],

107

[3, 3, 6]

108

])('adds %i + %i = %i', (a, b, expected) => {

109

expect(a + b).toBe(expected);

110

});

111

```

112

113

## Lifecycle Hooks

114

115

```typescript { .api }

116

function beforeAll(fn: () => void | Promise<void>, timeout?: number): void;

117

function afterAll(fn: () => void | Promise<void>, timeout?: number): void;

118

function beforeEach(fn: (context: TestContext) => void | Promise<void>, timeout?: number): void;

119

function afterEach(fn: (context: TestContext) => void | Promise<void>, timeout?: number): void;

120

```

121

122

| Hook | Runs | Use Case |

123

|------|------|----------|

124

| `beforeAll` | Once before all tests | Expensive setup (DB connection) |

125

| `afterAll` | Once after all tests | Cleanup (close connections) |

126

| `beforeEach` | Before each test | Reset state, create instances |

127

| `afterEach` | After each test | Cleanup, restore mocks |

128

129

**Example:**

130

```typescript

131

describe('Database tests', () => {

132

let db;

133

134

beforeAll(async () => {

135

db = await connectToDatabase();

136

await db.migrate();

137

});

138

139

afterAll(async () => {

140

await db.close();

141

});

142

143

beforeEach(async () => {

144

await db.clear();

145

await db.seed();

146

});

147

148

afterEach(async () => {

149

await db.rollback();

150

});

151

152

test('creates user', async () => {

153

const user = await db.createUser({ name: 'John' });

154

expect(user.id).toBeDefined();

155

});

156

157

test('finds user', async () => {

158

await db.createUser({ name: 'John' });

159

const user = await db.findUser('John');

160

expect(user).toBeDefined();

161

});

162

});

163

```

164

165

## Test Event Handlers

166

167

```typescript { .api }

168

function onTestFailed(handler: (result: RunnerTestCase, error: unknown) => void | Promise<void>): void;

169

function onTestFinished(handler: (result: RunnerTestCase) => void | Promise<void>): void;

170

```

171

172

**Example:**

173

```typescript

174

test('with event handlers', () => {

175

onTestFailed((result, error) => {

176

console.log(`Test "${result.name}" failed:`, error);

177

});

178

179

onTestFinished((result) => {

180

console.log(`Test "${result.name}" finished: ${result.result?.state}`);

181

});

182

183

expect(1).toBe(1);

184

});

185

```

186

187

## Test Modifiers API

188

189

```typescript { .api }

190

interface TestAPI {

191

only: TestAPI;

192

skip: TestAPI;

193

todo: TestAPI;

194

skipIf(condition: boolean): TestAPI;

195

runIf(condition: boolean): TestAPI;

196

concurrent: TestAPI;

197

sequential: TestAPI;

198

fails: TestAPI;

199

each<T>(cases: T[]): (name: string, fn: (...args: T[]) => void) => void;

200

}

201

202

interface SuiteAPI {

203

only: SuiteAPI;

204

skip: SuiteAPI;

205

todo: SuiteAPI;

206

skipIf(condition: boolean): SuiteAPI;

207

runIf(condition: boolean): SuiteAPI;

208

concurrent: SuiteAPI;

209

sequential: SuiteAPI;

210

}

211

```

212

213

## Execution Control Patterns

214

215

### Focus on Specific Tests

216

217

```typescript

218

describe('feature', () => {

219

test.only('this runs', () => { /* only this */ });

220

test('skipped', () => { /* skipped */ });

221

});

222

223

describe.only('focused suite', () => {

224

test('runs', () => { /* runs because suite focused */ });

225

});

226

```

227

228

### Skip Tests

229

230

```typescript

231

test.skip('not ready', () => { /* skipped */ });

232

233

describe.skip('disabled suite', () => {

234

test('also skipped', () => { /* skipped */ });

235

});

236

```

237

238

### Parallel vs Sequential

239

240

```typescript

241

// Parallel (for independent tests)

242

describe.concurrent('parallel', () => {

243

test('test 1', async () => { await delay(100); });

244

test('test 2', async () => { await delay(100); });

245

// Both run simultaneously

246

});

247

248

// Sequential (default, for tests with shared state)

249

describe.sequential('sequential', () => {

250

test('test 1', () => { /* runs first */ });

251

test('test 2', () => { /* runs second */ });

252

});

253

```

254

255

## Type Definitions

256

257

```typescript { .api }

258

interface RunnerTestCase {

259

id: string;

260

name: string;

261

mode: 'run' | 'skip' | 'only' | 'todo';

262

suite?: RunnerTestSuite;

263

result?: RunnerTaskResult;

264

meta: Record<string, any>;

265

}

266

267

interface RunnerTestSuite {

268

id: string;

269

name: string;

270

mode: 'run' | 'skip' | 'only' | 'todo';

271

tasks: (RunnerTestCase | RunnerTestSuite)[];

272

meta: Record<string, any>;

273

}

274

275

interface RunnerTaskResult {

276

state: 'pass' | 'fail' | 'skip';

277

duration?: number;

278

error?: unknown;

279

retryCount?: number;

280

}

281

282

interface TaskCustomOptions {

283

timeout?: number;

284

retry?: number;

285

repeats?: number;

286

concurrent?: boolean;

287

}

288

```

289

290

## Common Patterns

291

292

### Parameterized Tests

293

294

```typescript

295

// Array of inputs

296

test.each([

297

{ input: 'hello', expected: 5 },

298

{ input: 'world', expected: 5 },

299

{ input: 'foo', expected: 3 }

300

])('length of "$input" is $expected', ({ input, expected }) => {

301

expect(input.length).toBe(expected);

302

});

303

304

// Multiple arguments

305

test.each([

306

[1, 1, 2],

307

[1, 2, 3],

308

[2, 1, 3]

309

])('.add(%i, %i) = %i', (a, b, expected) => {

310

expect(a + b).toBe(expected);

311

});

312

```

313

314

### Conditional Tests

315

316

```typescript

317

const isCI = !!process.env.CI;

318

const isWindows = process.platform === 'win32';

319

320

test.skipIf(isWindows)('Unix-specific feature', () => { ... });

321

test.runIf(isCI)('CI-only test', () => { ... });

322

test.skipIf(!process.env.API_KEY)('needs API key', () => { ... });

323

```

324

325

### Shared Setup with Context

326

327

```typescript

328

describe('user operations', () => {

329

let user;

330

331

beforeEach(() => {

332

user = createUser();

333

});

334

335

test('updates name', () => {

336

user.name = 'John';

337

expect(user.name).toBe('John');

338

});

339

340

test('updates email', () => {

341

user.email = 'john@example.com';

342

expect(user.email).toBe('john@example.com');

343

});

344

});

345

```

346

347

## Best Practices

348

349

1. **Use `beforeEach` for test isolation** - Each test gets fresh state

350

2. **Use `beforeAll` for expensive setup** - Database connections, file reads

351

3. **Always clean up in `afterEach/afterAll`** - Prevent test pollution

352

4. **Use `.only` during debugging** - Focus on specific failing tests

353

5. **Use `.concurrent` for independent tests** - Faster test execution

354

6. **Use `.sequential` for shared state** - Tests that modify shared resources

355

7. **Use `.each` for similar tests** - Reduce code duplication

356

357

## Lifecycle Order

358

359

```

360

beforeAll (suite)

361

├─ beforeEach (test 1)

362

│ ├─ test 1

363

│ └─ afterEach (test 1)

364

├─ beforeEach (test 2)

365

│ ├─ test 2

366

│ └─ afterEach (test 2)

367

└─ afterAll (suite)

368

```

369

370

Nested suites:

371

```

372

beforeAll (outer)

373

├─ beforeEach (outer)

374

│ ├─ beforeAll (inner)

375

│ │ ├─ beforeEach (inner)

376

│ │ │ ├─ test

377

│ │ │ └─ afterEach (inner)

378

│ │ └─ afterAll (inner)

379

│ └─ afterEach (outer)

380

└─ afterAll (outer)

381

```

382