or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mdconfiguration.mdexceptions.mdfixtures.mdindex.mdtest-suites.mdtransforms.md

transforms.mddocs/

0

# Transforms and Extensions

1

2

Extensible transform system for customizing test and suite behavior. Transforms allow you to modify individual tests, entire suites, and return value handling without changing the core test definitions.

3

4

## Capabilities

5

6

### TestTransforms - Individual Test Transformations

7

8

Transform individual tests to modify their behavior, add metadata, or implement custom logic.

9

10

```scala { .api }

11

/**

12

* Trait providing test transformation capabilities (mixed into BaseFunSuite)

13

*/

14

trait TestTransforms {

15

/**

16

* Get the list of test transforms to apply

17

* Override this method to customize test transformation

18

*/

19

def munitTestTransforms: List[TestTransform]

20

21

/**

22

* Apply test transforms to a single test

23

* This method is called automatically for each test

24

*/

25

def munitTestTransform(test: Test): Test

26

27

/** Check if flaky tests should be treated as passing */

28

def munitFlakyOK: Boolean

29

}

30

31

/**

32

* A transformation that can be applied to individual tests

33

* @param name Descriptive name for the transform

34

* @param fn Function that transforms a Test into a modified Test

35

*/

36

final class TestTransform(val name: String, fn: Test => Test) extends Function1[Test, Test] {

37

def apply(test: Test): Test = fn(test)

38

}

39

```

40

41

**Built-in Test Transforms:**

42

43

```scala { .api }

44

/** Transform that handles tests marked with the Fail tag */

45

def munitFailTransform: TestTransform

46

47

/** Transform that handles tests marked with the Flaky tag */

48

def munitFlakyTransform: TestTransform

49

50

/**

51

* Transform that appends additional context to failure messages

52

* @param buildSuffix Function to generate additional context for a test

53

*/

54

def munitAppendToFailureMessage(buildSuffix: Test => Option[String]): TestTransform

55

```

56

57

**Usage Examples:**

58

59

```scala

60

class TransformExamples extends FunSuite {

61

// Custom transform to add timing information

62

val timingTransform = new TestTransform(

63

"timing",

64

test => test.withBodyMap { originalBody =>

65

val start = System.currentTimeMillis()

66

originalBody.andThen { _ =>

67

val duration = System.currentTimeMillis() - start

68

println(s"Test '${test.name}' took ${duration}ms")

69

Future.unit

70

}

71

}

72

)

73

74

// Custom transform to retry flaky tests

75

val retryTransform = new TestTransform(

76

"retry",

77

test => {

78

if (test.tags.contains(new Tag("retry"))) {

79

test.withBodyMap { originalBody =>

80

originalBody.recoverWith {

81

case _: AssertionError =>

82

println(s"Retrying test: ${test.name}")

83

originalBody

84

}

85

}

86

} else {

87

test

88

}

89

}

90

)

91

92

override def munitTestTransforms: List[TestTransform] =

93

super.munitTestTransforms ++ List(timingTransform, retryTransform)

94

95

test("normal test") {

96

assertEquals(1 + 1, 2)

97

}

98

99

test("flaky network test".tag(new Tag("retry"))) {

100

// This test will be retried once if it fails

101

callExternalAPI()

102

}

103

}

104

```

105

106

### SuiteTransforms - Suite-Level Transformations

107

108

Transform entire test suites to filter, reorder, or modify collections of tests.

109

110

```scala { .api }

111

/**

112

* Trait providing suite transformation capabilities (mixed into BaseFunSuite)

113

*/

114

trait SuiteTransforms {

115

/**

116

* Get the list of suite transforms to apply

117

* Override this method to customize suite transformation

118

*/

119

def munitSuiteTransforms: List[SuiteTransform]

120

121

/**

122

* Apply suite transforms to the entire test list

123

* This method is called automatically when collecting tests

124

*/

125

def munitSuiteTransform(tests: List[Test]): List[Test]

126

127

/** Override to ignore the entire test suite */

128

def munitIgnore: Boolean = false

129

130

/** Check if running in continuous integration environment */

131

def isCI: Boolean

132

}

133

134

/**

135

* A transformation that can be applied to entire test suites

136

* @param name Descriptive name for the transform

137

* @param fn Function that transforms a List[Test] into a modified List[Test]

138

*/

139

final class SuiteTransform(val name: String, fn: List[Test] => List[Test]) extends Function1[List[Test], List[Test]] {

140

def apply(tests: List[Test]): List[Test] = fn(tests)

141

}

142

```

143

144

**Built-in Suite Transforms:**

145

146

```scala { .api }

147

/** Transform that handles ignored test suites */

148

def munitIgnoreSuiteTransform: SuiteTransform

149

150

/** Transform that handles the Only tag (runs only marked tests) */

151

def munitOnlySuiteTransform: SuiteTransform

152

```

153

154

**Usage Examples:**

155

156

```scala

157

class SuiteTransformExamples extends FunSuite {

158

// Custom transform to randomize test order

159

val randomizeTransform = new SuiteTransform(

160

"randomize",

161

tests => scala.util.Random.shuffle(tests)

162

)

163

164

// Custom transform to group tests by tags

165

val groupByTagTransform = new SuiteTransform(

166

"groupByTag",

167

tests => {

168

val (slowTests, fastTests) = tests.partition(_.tags.contains(Slow))

169

fastTests ++ slowTests // Run fast tests first

170

}

171

)

172

173

// Custom transform to skip integration tests in CI

174

val skipIntegrationInCI = new SuiteTransform(

175

"skipIntegrationInCI",

176

tests => {

177

if (isCI) {

178

tests.filterNot(_.tags.contains(new Tag("integration")))

179

} else {

180

tests

181

}

182

}

183

)

184

185

override def munitSuiteTransforms: List[SuiteTransform] =

186

super.munitSuiteTransforms ++ List(

187

groupByTagTransform,

188

skipIntegrationInCI

189

)

190

191

test("fast unit test") {

192

assertEquals(1 + 1, 2)

193

}

194

195

test("slow integration test".tag(Slow).tag(new Tag("integration"))) {

196

// This test may be skipped in CI

197

performIntegrationTest()

198

}

199

}

200

```

201

202

### ValueTransforms - Return Value Transformations

203

204

Transform test return values to handle different types of results (Future, Try, etc.) and convert them to the standard `Future[Any]` format.

205

206

```scala { .api }

207

/**

208

* Trait providing value transformation capabilities (mixed into BaseFunSuite)

209

*/

210

trait ValueTransforms {

211

/**

212

* Get the list of value transforms to apply

213

* Override this method to customize value transformation

214

*/

215

def munitValueTransforms: List[ValueTransform]

216

217

/**

218

* Apply value transforms to convert any test return value to Future[Any]

219

* This method is called automatically for each test body

220

*/

221

def munitValueTransform(testValue: => Any): Future[Any]

222

}

223

224

/**

225

* A transformation that can be applied to test return values

226

* @param name Descriptive name for the transform

227

* @param fn Partial function that transforms specific types to Future[Any]

228

*/

229

final class ValueTransform(val name: String, fn: PartialFunction[Any, Future[Any]]) extends Function1[Any, Option[Future[Any]]] {

230

def apply(value: Any): Option[Future[Any]] = fn.lift(value)

231

}

232

```

233

234

**Built-in Value Transforms:**

235

236

```scala { .api }

237

/** Transform that handles Future return values */

238

def munitFutureTransform: ValueTransform

239

```

240

241

**Usage Examples:**

242

243

```scala

244

import scala.util.{Try, Success, Failure}

245

import scala.concurrent.Future

246

247

class ValueTransformExamples extends FunSuite {

248

// Custom transform to handle Try values

249

val tryTransform = new ValueTransform(

250

"try",

251

{

252

case Success(value) => Future.successful(value)

253

case Failure(exception) => Future.failed(exception)

254

}

255

)

256

257

// Custom transform to handle custom Result type

258

case class Result[T](value: T, errors: List[String]) {

259

def isSuccess: Boolean = errors.isEmpty

260

}

261

262

val resultTransform = new ValueTransform(

263

"result",

264

{

265

case result: Result[_] =>

266

if (result.isSuccess) {

267

Future.successful(result.value)

268

} else {

269

Future.failed(new AssertionError(s"Result failed: ${result.errors.mkString(", ")}"))

270

}

271

}

272

)

273

274

override def munitValueTransforms: List[ValueTransform] =

275

super.munitValueTransforms ++ List(tryTransform, resultTransform)

276

277

test("test returning Try") {

278

Try {

279

assertEquals(2 + 2, 4)

280

"success"

281

}

282

}

283

284

test("test returning custom Result") {

285

val data = processData()

286

Result(data, List.empty) // Success case

287

}

288

289

test("test returning failed Result") {

290

Result("", List("validation error")) // This will fail the test

291

}

292

}

293

```

294

295

## Advanced Transform Patterns

296

297

### Conditional Transforms

298

299

```scala

300

class ConditionalTransforms extends FunSuite {

301

// Transform that only applies to tests with specific tags

302

val debugTransform = new TestTransform(

303

"debug",

304

test => {

305

if (test.tags.contains(new Tag("debug"))) {

306

test.withBodyMap { originalBody =>

307

println(s"[DEBUG] Starting test: ${test.name}")

308

originalBody.andThen { result =>

309

println(s"[DEBUG] Completed test: ${test.name}")

310

result

311

}

312

}

313

} else {

314

test

315

}

316

}

317

)

318

319

// Transform that modifies behavior based on environment

320

val environmentTransform = new SuiteTransform(

321

"environment",

322

tests => {

323

val environment = System.getProperty("test.environment", "local")

324

environment match {

325

case "ci" => tests.filterNot(_.tags.contains(new Tag("local-only")))

326

case "staging" => tests.filterNot(_.tags.contains(new Tag("prod-only")))

327

case _ => tests

328

}

329

}

330

)

331

}

332

```

333

334

### Metrics and Reporting

335

336

```scala

337

class MetricsTransforms extends FunSuite {

338

private val testMetrics = scala.collection.mutable.Map[String, Long]()

339

340

val metricsTransform = new TestTransform(

341

"metrics",

342

test => test.withBodyMap { originalBody =>

343

val start = System.nanoTime()

344

originalBody.andThen { result =>

345

val duration = System.nanoTime() - start

346

testMetrics(test.name) = duration

347

result

348

}

349

}

350

)

351

352

override def munitTestTransforms = List(metricsTransform)

353

354

override def afterAll(): Unit = {

355

super.afterAll()

356

println("Test Execution Times:")

357

testMetrics.toList.sortBy(_._2).foreach { case (name, nanos) =>

358

println(f" $name: ${nanos / 1000000.0}%.2f ms")

359

}

360

}

361

}

362

```

363

364

### Error Context Enhancement

365

366

```scala

367

class ErrorContextTransforms extends FunSuite {

368

val contextTransform = new TestTransform(

369

"context",

370

test => test.withBodyMap { originalBody =>

371

originalBody.recoverWith {

372

case e: AssertionError =>

373

val enhancedMessage = s"${e.getMessage}\n" +

374

s"Test: ${test.name}\n" +

375

s"Tags: ${test.tags.map(_.value).mkString(", ")}\n" +

376

s"Location: ${test.location}"

377

Future.failed(new AssertionError(enhancedMessage, e))

378

}

379

}

380

)

381

382

override def munitTestTransforms = List(contextTransform)

383

}

384

```

385

386

## Transform Configuration

387

388

### Environment-Based Configuration

389

390

```scala

391

class EnvironmentBasedSuite extends FunSuite {

392

override def munitFlakyOK: Boolean = {

393

// Allow flaky tests in development but not in CI

394

!isCI && System.getProperty("munit.flaky.ok", "false").toBoolean

395

}

396

397

override def isCI: Boolean = {

398

System.getenv("CI") != null ||

399

System.getProperty("CI") != null

400

}

401

}

402

```

403

404

### Custom Transform Ordering

405

406

The order of transforms matters - they are applied in sequence:

407

408

```scala

409

override def munitTestTransforms: List[TestTransform] = List(

410

timingTransform, // Applied first

411

retryTransform, // Applied second

412

loggingTransform // Applied last

413

) ++ super.munitTestTransforms // Include built-in transforms

414

```