or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

array-assertions.mdcore-assertions.mdexception-handling.mdhamcrest-matchers.mdindex.mdtest-assumptions.mdtest-lifecycle.mdtest-runners.md

test-runners.mddocs/

0

# Test Runners

1

2

JUnit provides a flexible runner framework that allows custom test execution strategies. The Scala.js implementation includes SBT testing framework integration and support for custom runners through annotations.

3

4

## SBT Framework Integration

5

6

### JUnitFramework

7

8

```scala { .api }

9

final class JUnitFramework extends Framework {

10

val name: String = "Scala.js JUnit test framework"

11

def fingerprints(): Array[Fingerprint]

12

def runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader): Runner

13

def slaveRunner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader, send: String => Unit): Runner

14

}

15

```

16

17

The JUnitFramework class integrates JUnit tests with the SBT testing framework, enabling test discovery and execution in Scala.js projects.

18

19

**Framework Properties:**

20

```scala

21

val framework = new JUnitFramework()

22

println(framework.name) // "Scala.js JUnit test framework"

23

```

24

25

**Test Discovery:**

26

```scala

27

val fingerprints = framework.fingerprints()

28

// Returns array containing fingerprint for @org.junit.Test annotation

29

```

30

31

**Runner Creation:**

32

```scala

33

val args = Array("-v", "-s") // Verbose mode, decode Scala names

34

val runner = framework.runner(args, Array.empty, classLoader)

35

```

36

37

### Test Discovery Fingerprint

38

39

```scala { .api }

40

trait Fingerprint {

41

def requireNoArgConstructor(): Boolean

42

def isModule(): Boolean

43

}

44

45

// JUnit-specific fingerprint

46

object JUnitFingerprint extends AnnotatedFingerprint {

47

def annotationName(): String = "org.junit.Test"

48

def isModule(): Boolean = false

49

}

50

```

51

52

## Runner Hierarchy

53

54

### Base Runner

55

56

```scala { .api }

57

abstract class Runner {

58

def done(): String

59

def tasks(taskDefs: Array[TaskDef]): Array[Task]

60

}

61

62

// Internal test metadata and execution support

63

trait Bootstrapper {

64

def beforeClass(): Unit

65

def afterClass(): Unit

66

def before(instance: AnyRef): Unit

67

def after(instance: AnyRef): Unit

68

def tests(): Array[TestMetadata]

69

def invokeTest(instance: AnyRef, name: String): Future[Try[Unit]]

70

def newInstance(): AnyRef

71

}

72

73

final class TestMetadata(val name: String, val ignored: Boolean, val annotation: org.junit.Test)

74

```

75

76

### ParentRunner

77

78

```scala { .api }

79

abstract class ParentRunner[T](testClass: Class[_]) extends Runner {

80

// Base implementation for runners that have child tests

81

}

82

```

83

84

### BlockJUnit4ClassRunner

85

86

```scala { .api }

87

class BlockJUnit4ClassRunner(testClass: Class[_]) extends ParentRunner[FrameworkMethod] {

88

// Standard JUnit 4 test class runner (dummy implementation for Scala.js)

89

}

90

```

91

92

### JUnit4 Runner

93

94

```scala { .api }

95

final class JUnit4(klass: Class[_]) extends BlockJUnit4ClassRunner(klass) {

96

// Default JUnit 4 runner

97

}

98

```

99

100

## Custom Runner Support

101

102

### @RunWith Annotation

103

104

```scala { .api }

105

class RunWith(value: Class[_ <: Runner]) extends StaticAnnotation with Annotation {

106

def annotationType(): Class[_ <: Annotation] = classOf[RunWith]

107

}

108

```

109

110

**Usage:**

111

```scala

112

@RunWith(classOf[CustomTestRunner])

113

class SpecialTest {

114

@Test

115

def shouldRunWithCustomRunner(): Unit = {

116

// This test will be executed by CustomTestRunner

117

assertTrue(true)

118

}

119

}

120

```

121

122

**Parameterized Test Example:**

123

```scala

124

@RunWith(classOf[ParameterizedRunner])

125

class ParameterizedTest(value: Int) {

126

@Test

127

def shouldTestWithParameter(): Unit = {

128

assertTrue(s"Value $value should be positive", value > 0)

129

}

130

}

131

```

132

133

## Runner Configuration

134

135

### Command Line Arguments

136

137

The JUnitFramework supports various command line arguments for test execution control:

138

139

```scala

140

val args = Array(

141

"-v", // Verbose output

142

"-n", // No color output

143

"-s", // Decode Scala names

144

"-a", // Log assert messages

145

"-c" // Don't log exception class names

146

)

147

```

148

149

**Argument Processing:**

150

```scala

151

// Enable verbose mode

152

val verboseArgs = Array("-v")

153

val runner = framework.runner(verboseArgs, Array.empty, classLoader)

154

155

// Disable colored output for CI environments

156

val ciArgs = Array("-n")

157

val ciRunner = framework.runner(ciArgs, Array.empty, classLoader)

158

```

159

160

### RunSettings Configuration

161

162

```scala { .api }

163

case class RunSettings(

164

color: Boolean, // Enable colored output

165

decodeScalaNames: Boolean, // Decode Scala names in stack traces

166

verbose: Boolean, // Verbose logging

167

logAssert: Boolean, // Log assertion failures

168

notLogExceptionClass: Boolean // Don't log exception class names

169

)

170

```

171

172

**Usage in Custom Runners:**

173

```scala

174

class CustomRunner(args: Array[String], remoteArgs: Array[String], settings: RunSettings) {

175

def executeTest(testMethod: Method): Unit = {

176

if (settings.verbose) {

177

println(s"Running test: ${settings.decodeName(testMethod.getName)}")

178

}

179

180

try {

181

testMethod.invoke(testInstance)

182

if (settings.verbose) {

183

println("Test passed")

184

}

185

} catch {

186

case e: AssertionError =>

187

if (settings.logAssert) {

188

println(s"Assertion failed: ${e.getMessage}")

189

}

190

throw e

191

}

192

}

193

}

194

```

195

196

## Test Execution Flow

197

198

### Task-Based Execution

199

200

```scala { .api }

201

trait Task {

202

def taskDef(): TaskDef

203

def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task]

204

}

205

206

trait TaskDef {

207

def fullyQualifiedName(): String

208

def fingerprint(): Fingerprint

209

def explicitlySpecified(): Boolean

210

def selectors(): Array[Selector]

211

}

212

```

213

214

**Example Test Execution:**

215

```scala

216

class JUnitTask(taskDef: TaskDef, settings: RunSettings) extends Task {

217

def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = {

218

val reporter = new Reporter(eventHandler, loggers, settings, taskDef)

219

220

try {

221

reporter.reportRunStarted()

222

223

val testClass = Class.forName(taskDef.fullyQualifiedName())

224

val testInstance = testClass.newInstance()

225

226

// Execute @Before methods

227

executeBeforeMethods(testInstance)

228

229

// Execute test methods

230

val testMethods = findTestMethods(testClass)

231

testMethods.foreach(executeTestMethod(testInstance, _, reporter))

232

233

// Execute @After methods

234

executeAfterMethods(testInstance)

235

236

reporter.reportRunFinished(failedCount, ignoredCount, totalCount, duration)

237

238

} catch {

239

case e: Exception =>

240

reporter.reportErrors("Test execution failed", None, 0.0, List(e))

241

}

242

243

Array.empty // No subtasks

244

}

245

}

246

```

247

248

## Event Reporting

249

250

### JUnitEvent

251

252

```scala { .api }

253

class JUnitEvent(

254

taskDef: TaskDef,

255

status: Status,

256

selector: Selector,

257

throwable: OptionalThrowable = new OptionalThrowable,

258

duration: Long = -1L

259

) extends Event {

260

def fullyQualifiedName(): String = taskDef.fullyQualifiedName()

261

def fingerprint(): Fingerprint = taskDef.fingerprint()

262

}

263

```

264

265

**Event Status Types:**

266

- `Status.Success` - Test passed

267

- `Status.Failure` - Test failed with assertion error

268

- `Status.Error` - Test failed with unexpected exception

269

- `Status.Skipped` - Test was ignored or assumption failed

270

271

### Reporter Integration

272

273

```scala { .api }

274

class Reporter(

275

eventHandler: EventHandler,

276

loggers: Array[Logger],

277

settings: RunSettings,

278

taskDef: TaskDef

279

) {

280

def reportTestStarted(method: String): Unit

281

def reportTestFinished(method: String, succeeded: Boolean, timeInSeconds: Double): Unit

282

def reportErrors(prefix: String, method: Option[String], timeInSeconds: Double, errors: List[Throwable]): Unit

283

def reportIgnored(method: Option[String]): Unit

284

}

285

```

286

287

## Custom Runner Implementation

288

289

```scala

290

class CustomParameterizedRunner(testClass: Class[_]) extends Runner {

291

private val parameters = getParameters(testClass)

292

293

def done(): String = ""

294

295

def tasks(taskDefs: Array[TaskDef]): Array[Task] = {

296

taskDefs.flatMap { taskDef =>

297

parameters.map { param =>

298

new ParameterizedTask(taskDef, param)

299

}

300

}

301

}

302

303

private def getParameters(clazz: Class[_]): Array[Any] = {

304

// Extract parameters from @Parameters annotation or method

305

Array(1, 2, 3, 4, 5) // Example parameters

306

}

307

}

308

309

class ParameterizedTask(taskDef: TaskDef, parameter: Any) extends Task {

310

def taskDef(): TaskDef = taskDef

311

312

def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = {

313

// Execute test with parameter

314

val testClass = Class.forName(taskDef.fullyQualifiedName())

315

val constructor = testClass.getConstructor(parameter.getClass)

316

val testInstance = constructor.newInstance(parameter)

317

318

// Execute test methods on parameterized instance

319

executeTestMethods(testInstance, eventHandler, loggers)

320

321

Array.empty

322

}

323

}

324

```

325

326

## Integration with Build Tools

327

328

### SBT Integration

329

330

```scala

331

// build.sbt

332

libraryDependencies += "org.scala-js" %%% "scalajs-junit-test-runtime" % "1.19.0" % Test

333

334

// Enable JUnit testing

335

testFrameworks += new TestFramework("org.scalajs.junit.JUnitFramework")

336

```

337

338

### Test Configuration

339

340

```scala

341

// Configure test execution

342

Test / testOptions += Tests.Argument(

343

TestFrameworks.JUnit,

344

"-v", // Verbose

345

"-s", // Decode Scala names

346

"-a" // Log assertions

347

)

348

```

349

350

## Advanced Runner Features

351

352

### Parallel Execution Support

353

354

```scala

355

class ParallelJUnitRunner extends Runner {

356

def tasks(taskDefs: Array[TaskDef]): Array[Task] = {

357

taskDefs.map(new ParallelTask(_))

358

}

359

}

360

361

class ParallelTask(taskDef: TaskDef) extends Task {

362

def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = {

363

// Execute test in parallel-safe manner

364

Future {

365

executeTestSafely(eventHandler, loggers)

366

}

367

Array.empty

368

}

369

}

370

```

371

372

### Test Filtering

373

374

```scala

375

class FilteringRunner(filter: String => Boolean) extends Runner {

376

def tasks(taskDefs: Array[TaskDef]): Array[Task] = {

377

taskDefs

378

.filter(taskDef => filter(taskDef.fullyQualifiedName()))

379

.map(new FilteredTask(_))

380

}

381

}

382

```

383

384

The runner framework provides flexibility for custom test execution strategies while maintaining compatibility with standard JUnit test discovery and reporting mechanisms.