or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mdcore-dsl.mdindex.mdmock-testing.mdproperty-testing.mdspecifications.mdtest-aspects.mdtest-environment.md

test-aspects.mddocs/

0

# Test Aspects

1

2

Cross-cutting concerns for tests including timeouts, retries, conditional execution, and test organization. Test aspects allow you to modify test behavior without changing test logic.

3

4

## Capabilities

5

6

### Core Test Aspect Class

7

8

Base class for creating and composing test aspects.

9

10

```scala { .api }

11

/**

12

* TestAspect transforms test specifications by modifying their behavior.

13

* Aspects can change environment requirements, error types, or execution characteristics.

14

*/

15

abstract class TestAspect[+LowerR, -UpperR, +LowerE, -UpperE] {

16

/**

17

* Apply aspect to some tests in a spec based on predicate

18

* @param spec - Test specification to transform

19

* @returns Modified specification

20

*/

21

def some[R >: LowerR <: UpperR, E >: LowerE <: UpperE](spec: ZSpec[R, E]): ZSpec[R, E]

22

23

/**

24

* Apply aspect to all tests in a spec

25

* @param spec - Test specification to transform

26

* @returns Modified specification

27

*/

28

def all[R >: LowerR <: UpperR, E >: LowerE <: UpperE](spec: ZSpec[R, E]): ZSpec[R, E]

29

30

/**

31

* Compose this aspect with another aspect sequentially

32

* @param that - Aspect to compose with

33

* @returns Combined aspect

34

*/

35

def >>>[LowerR1 >: LowerR, UpperR1 <: UpperR, LowerE1 >: LowerE, UpperE1 <: UpperE](

36

that: TestAspect[LowerR1, UpperR1, LowerE1, UpperE1]

37

): TestAspect[LowerR1, UpperR1, LowerE1, UpperE1]

38

}

39

40

// Type aliases for common aspect patterns

41

type TestAspectPoly = TestAspect[Nothing, Any, Nothing, Any]

42

type TestAspectAtLeastR[R] = TestAspect[Nothing, R, Nothing, Any]

43

```

44

45

**Aspect Application Syntax:**

46

47

```scala { .api }

48

// Apply aspect to spec using @@ operator

49

spec @@ aspect

50

51

// Example usage

52

test("flaky test") {

53

assert(randomResult())(isTrue)

54

} @@ TestAspect.flaky @@ TestAspect.timeout(30.seconds)

55

```

56

57

### Basic Aspects

58

59

Fundamental aspects for test control.

60

61

```scala { .api }

62

/**

63

* Identity aspect that leaves tests unchanged

64

*/

65

val identity: TestAspectPoly

66

67

/**

68

* Ignore tests - marks them as ignored in results

69

*/

70

val ignore: TestAspectAtLeastR[Annotations]

71

```

72

73

**Usage Examples:**

74

75

```scala

76

// Temporarily ignore failing tests

77

test("broken test") {

78

assert(brokenFunction())(equalTo(42))

79

} @@ TestAspect.ignore

80

81

// Conditional ignoring

82

val aspectToUse = if (isCI) TestAspect.ignore else TestAspect.identity

83

test("local only test") {

84

// test logic

85

} @@ aspectToUse

86

```

87

88

### Lifecycle Aspects

89

90

Aspects for running effects before, after, or around tests.

91

92

```scala { .api }

93

/**

94

* Run effect before each test

95

* @param effect - Effect to run before test

96

* @returns Aspect that runs effect before each test

97

*/

98

def before[R0, E0](effect: ZIO[R0, E0, Any]): TestAspect[Nothing, R0, E0, Any]

99

100

/**

101

* Run effect after each test

102

* @param effect - Effect to run after test

103

* @returns Aspect that runs effect after each test

104

*/

105

def after[R0, E0](effect: ZIO[R0, E0, Any]): TestAspect[Nothing, R0, E0, Any]

106

107

/**

108

* Run effect before all tests in suite

109

* @param effect - Effect to run once before suite

110

* @returns Aspect that runs effect before suite

111

*/

112

def beforeAll[R0, E0](effect: ZIO[R0, E0, Any]): TestAspect[Nothing, R0, E0, Any]

113

114

/**

115

* Run effect after all tests in suite

116

* @param effect - Effect to run once after suite

117

* @returns Aspect that runs effect after suite

118

*/

119

def afterAll[R0, E0](effect: ZIO[R0, E0, Any]): TestAspect[Nothing, R0, E0, Any]

120

121

/**

122

* Run setup effect before and cleanup effect after each test

123

* @param before - Setup effect that produces resource

124

* @param after - Cleanup effect that consumes resource

125

* @returns Aspect that manages resource lifecycle

126

*/

127

def around[R0, E0, A](before: ZIO[R0, E0, A])(after: A => ZIO[R0, Nothing, Any]): TestAspect[Nothing, R0, E0, Any]

128

129

/**

130

* Run setup effect before and cleanup effect after all tests

131

* @param before - Setup effect that produces resource

132

* @param after - Cleanup effect that consumes resource

133

* @returns Aspect that manages suite-level resource lifecycle

134

*/

135

def aroundAll[R0, E0, A](before: ZIO[R0, E0, A])(after: A => ZIO[R0, Nothing, Any]): TestAspect[Nothing, R0, E0, Any]

136

```

137

138

**Usage Examples:**

139

140

```scala

141

// Database test setup/teardown

142

val dbAspect = TestAspect.before(setupDatabase) >>> TestAspect.after(cleanupDatabase)

143

144

suite("database tests")(

145

test("user creation") { /* test */ },

146

test("user deletion") { /* test */ }

147

) @@ dbAspect

148

149

// Resource management

150

val serverAspect = TestAspect.aroundAll(startTestServer) { server =>

151

server.shutdown()

152

}

153

154

// Per-test cleanup

155

test("file operations") {

156

// test that creates files

157

} @@ TestAspect.after(cleanupTempFiles)

158

```

159

160

### Timeout Aspects

161

162

Aspects for controlling test timeouts and timing warnings.

163

164

```scala { .api }

165

/**

166

* Timeout tests after specified duration

167

* @param duration - Maximum time to allow test to run

168

* @returns Aspect that fails tests exceeding timeout

169

*/

170

def timeout(duration: Duration): TestAspectAtLeastR[Live]

171

172

/**

173

* Show warning if test takes longer than specified duration

174

* @param duration - Duration after which to show warning

175

* @returns Aspect that warns about slow tests

176

*/

177

def timeoutWarning(duration: Duration): TestAspectAtLeastR[Live with Annotations]

178

```

179

180

**Usage Examples:**

181

182

```scala

183

// Fast tests should complete quickly

184

test("quick operation") {

185

assert(fastComputation())(equalTo(42))

186

} @@ TestAspect.timeout(100.millis)

187

188

// Warn about slow integration tests

189

suite("integration tests")(

190

testM("external API call") {

191

for {

192

result <- callExternalAPI()

193

} yield assert(result)(isSuccess)

194

}

195

) @@ TestAspect.timeoutWarning(5.seconds)

196

```

197

198

### Execution Control Aspects

199

200

Aspects for controlling how tests are executed.

201

202

```scala { .api }

203

/**

204

* Mark tests as flaky (may fail intermittently)

205

*/

206

val flaky: TestAspectAtLeastR[Annotations]

207

208

/**

209

* Mark tests as non-flaky (should not fail intermittently)

210

*/

211

val nonFlaky: TestAspectAtLeastR[Annotations]

212

213

/**

214

* Execute tests sequentially (disable parallelism)

215

*/

216

val sequential: TestAspectPoly

217

218

/**

219

* Execute tests in parallel (enable parallelism)

220

*/

221

val parallel: TestAspectPoly

222

223

/**

224

* Retry test until it succeeds within time limit

225

* @param duration - Maximum time to keep retrying

226

* @returns Aspect that retries failing tests

227

*/

228

def eventually(duration: Duration): TestAspectAtLeastR[Live with Annotations]

229

230

/**

231

* Retry test according to schedule on failure

232

* @param schedule - Retry schedule

233

* @returns Aspect that retries with custom schedule

234

*/

235

def retry(schedule: Schedule[Any, TestFailure[Any], Any]): TestAspectPoly

236

```

237

238

**Usage Examples:**

239

240

```scala

241

// Mark network tests as potentially flaky

242

test("network connectivity") {

243

assert(pingServer())(isTrue)

244

} @@ TestAspect.flaky

245

246

// Retry flaky external service calls

247

test("external service") {

248

assert(callExternalService())(isSuccess)

249

} @@ TestAspect.retry(Schedule.recurs(3) && Schedule.exponential(100.millis))

250

251

// Database tests should run sequentially

252

suite("database tests")(

253

test("create user") { /* test */ },

254

test("update user") { /* test */ }

255

) @@ TestAspect.sequential

256

```

257

258

### Conditional Aspects

259

260

Aspects that apply conditionally based on environment or platform.

261

262

```scala { .api }

263

/**

264

* Apply aspect only on JavaScript platform

265

* @param aspect - Aspect to apply on JS

266

* @returns Aspect that only applies on JavaScript

267

*/

268

def js(aspect: TestAspectPoly): TestAspectPoly

269

270

/**

271

* Apply aspect only on JVM platform

272

* @param aspect - Aspect to apply on JVM

273

* @returns Aspect that only applies on JVM

274

*/

275

def jvm(aspect: TestAspectPoly): TestAspectPoly

276

277

/**

278

* Apply aspect only on Native platform

279

* @param aspect - Aspect to apply on Native

280

* @returns Aspect that only applies on Native

281

*/

282

def native(aspect: TestAspectPoly): TestAspectPoly

283

284

/**

285

* Apply different aspects based on environment variable

286

* @param env - Environment variable name

287

* @param ifTrue - Aspect to apply if variable is set

288

* @param ifFalse - Aspect to apply if variable is not set

289

* @returns Conditional aspect

290

*/

291

def ifEnv(env: String)(ifTrue: TestAspectPoly, ifFalse: TestAspectPoly): TestAspectPoly

292

293

/**

294

* Apply different aspects based on system property

295

* @param prop - System property name

296

* @param ifTrue - Aspect to apply if property is set

297

* @param ifFalse - Aspect to apply if property is not set

298

* @returns Conditional aspect

299

*/

300

def ifProp(prop: String)(ifTrue: TestAspectPoly, ifFalse: TestAspectPoly): TestAspectPoly

301

```

302

303

**Usage Examples:**

304

305

```scala

306

// Platform-specific timeouts

307

test("file I/O performance") {

308

assert(performFileIO())(isLessThan(100.millis))

309

} @@ TestAspect.jvm(TestAspect.timeout(50.millis))

310

@@ TestAspect.js(TestAspect.timeout(200.millis))

311

312

// Environment-based test behavior

313

test("feature flag test") {

314

assert(newFeatureEnabled())(isTrue)

315

} @@ TestAspect.ifEnv("ENABLE_NEW_FEATURE")(

316

TestAspect.identity,

317

TestAspect.ignore

318

)

319

```

320

321

### Annotation Aspects

322

323

Aspects for adding metadata and organizing tests.

324

325

```scala { .api }

326

/**

327

* Tag tests with labels for filtering and organization

328

* @param tag - Primary tag

329

* @param tags - Additional tags

330

* @returns Aspect that adds tags to tests

331

*/

332

def tag(tag: String, tags: String*): TestAspectPoly

333

334

/**

335

* Alias for tag

336

*/

337

def tagged(tag: String, tags: String*): TestAspectPoly

338

339

/**

340

* Track fiber information for debugging

341

*/

342

val fibers: TestAspectAtLeastR[Annotations]

343

344

/**

345

* Add diagnostic information for debugging test issues

346

*/

347

val diagnose: TestAspectAtLeastR[TestConsole with Annotations]

348

```

349

350

**Usage Examples:**

351

352

```scala

353

// Organization with tags

354

test("user authentication") {

355

// test logic

356

} @@ TestAspect.tag("integration", "auth", "security")

357

358

// Debugging failing tests

359

test("complex concurrent operation") {

360

// complex test logic

361

} @@ TestAspect.diagnose @@ TestAspect.fibers

362

363

// Filter tests by tags in CI

364

// Run with: testOnly * -- --tags integration

365

suite("all tests")(

366

test("unit test") { /* test */ } @@ TestAspect.tag("unit"),

367

test("integration test") { /* test */ } @@ TestAspect.tag("integration")

368

)

369

```

370

371

### Sample Configuration Aspects

372

373

Aspects for controlling property-based test generation.

374

375

```scala { .api }

376

/**

377

* Set number of samples for property-based tests

378

* @param n - Number of samples to generate

379

* @returns Aspect that configures sample count

380

*/

381

def samples(n: Int): TestAspectAtLeastR[Annotations]

382

383

/**

384

* Set maximum number of shrinking attempts

385

* @param n - Maximum shrink attempts

386

* @returns Aspect that configures shrinking

387

*/

388

def shrinks(n: Int): TestAspectAtLeastR[Annotations]

389

```

390

391

**Usage Examples:**

392

393

```scala

394

// More thorough property testing

395

test("commutative property") {

396

check(Gen.anyInt, Gen.anyInt) { (a, b) =>

397

assert(a + b)(equalTo(b + a))

398

}

399

} @@ TestAspect.samples(1000)

400

401

// Disable shrinking for faster feedback

402

test("performance property") {

403

check(Gen.listOf(Gen.anyInt)) { list =>

404

assert(sort(list).length)(equalTo(list.length))

405

}

406

} @@ TestAspect.shrinks(0)

407

```

408

409

### Aspect Composition

410

411

Combining multiple aspects effectively.

412

413

```scala { .api }

414

// Sequential composition with >>>

415

val composedAspect =

416

TestAspect.timeout(30.seconds) >>>

417

TestAspect.retry(Schedule.recurs(2)) >>>

418

TestAspect.tag("integration")

419

420

// Multiple aspects on single test

421

test("complex test") {

422

// test logic

423

} @@ TestAspect.flaky

424

@@ TestAspect.timeout(10.seconds)

425

@@ TestAspect.tag("slow")

426

@@ TestAspect.samples(200)

427

```

428

429

**Common Aspect Patterns:**

430

431

```scala

432

// Database test aspect

433

val dbTestAspect =

434

TestAspect.before(initDB) >>>

435

TestAspect.after(cleanDB) >>>

436

TestAspect.sequential >>>

437

TestAspect.tag("database")

438

439

// Flaky network test aspect

440

val networkTestAspect =

441

TestAspect.flaky >>>

442

TestAspect.retry(Schedule.recurs(3)) >>>

443

TestAspect.timeout(30.seconds) >>>

444

TestAspect.tag("network", "integration")

445

446

// Performance test aspect

447

val perfTestAspect =

448

TestAspect.samples(1000) >>>

449

TestAspect.timeout(60.seconds) >>>

450

TestAspect.tag("performance")

451

```

452

453

## Types

454

455

### Core Aspect Types

456

457

```scala { .api }

458

/**

459

* Base test aspect class with environment and error constraints

460

*/

461

abstract class TestAspect[+LowerR, -UpperR, +LowerE, -UpperE]

462

463

/**

464

* Polymorphic test aspect with no environment or error constraints

465

*/

466

type TestAspectPoly = TestAspect[Nothing, Any, Nothing, Any]

467

468

/**

469

* Test aspect that requires at least environment R

470

*/

471

type TestAspectAtLeastR[R] = TestAspect[Nothing, R, Nothing, Any]

472

```

473

474

### Per-Test Aspects

475

476

```scala { .api }

477

/**

478

* Base class for aspects that transform individual tests

479

*/

480

abstract class PerTest[+LowerR, -UpperR, +LowerE, -UpperE] extends TestAspect[LowerR, UpperR, LowerE, UpperE] {

481

/**

482

* Transform a single test

483

* @param test - Test to transform

484

* @returns Transformed test

485

*/

486

def perTest[R <: UpperR, E >: LowerE](test: ZIO[R, TestFailure[E], TestSuccess]): ZIO[R, TestFailure[E], TestSuccess]

487

}

488

```

489

490

### Annotation Types

491

492

```scala { .api }

493

/**

494

* Test annotation for adding metadata

495

*/

496

trait TestAnnotation[V] {

497

def identifier: String

498

def initial: V

499

def combine(v1: V, v2: V): V

500

}

501

502

/**

503

* Map of test annotations

504

*/

505

final case class TestAnnotationMap(

506

annotations: Map[TestAnnotation[Any], Any]

507

) {

508

def annotate[V](key: TestAnnotation[V], value: V): TestAnnotationMap

509

def get[V](key: TestAnnotation[V]): V

510

}

511

```