JUnit test runtime for Scala.js - provides the runtime infrastructure for running JUnit tests compiled with Scala.js
—
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.
final class JUnitFramework extends Framework {
val name: String = "Scala.js JUnit test framework"
def fingerprints(): Array[Fingerprint]
def runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader): Runner
def slaveRunner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader, send: String => Unit): Runner
}The JUnitFramework class integrates JUnit tests with the SBT testing framework, enabling test discovery and execution in Scala.js projects.
Framework Properties:
val framework = new JUnitFramework()
println(framework.name) // "Scala.js JUnit test framework"Test Discovery:
val fingerprints = framework.fingerprints()
// Returns array containing fingerprint for @org.junit.Test annotationRunner Creation:
val args = Array("-v", "-s") // Verbose mode, decode Scala names
val runner = framework.runner(args, Array.empty, classLoader)trait Fingerprint {
def requireNoArgConstructor(): Boolean
def isModule(): Boolean
}
// JUnit-specific fingerprint
object JUnitFingerprint extends AnnotatedFingerprint {
def annotationName(): String = "org.junit.Test"
def isModule(): Boolean = false
}abstract class Runner {
def done(): String
def tasks(taskDefs: Array[TaskDef]): Array[Task]
}
// Internal test metadata and execution support
trait Bootstrapper {
def beforeClass(): Unit
def afterClass(): Unit
def before(instance: AnyRef): Unit
def after(instance: AnyRef): Unit
def tests(): Array[TestMetadata]
def invokeTest(instance: AnyRef, name: String): Future[Try[Unit]]
def newInstance(): AnyRef
}
final class TestMetadata(val name: String, val ignored: Boolean, val annotation: org.junit.Test)abstract class ParentRunner[T](testClass: Class[_]) extends Runner {
// Base implementation for runners that have child tests
}class BlockJUnit4ClassRunner(testClass: Class[_]) extends ParentRunner[FrameworkMethod] {
// Standard JUnit 4 test class runner (dummy implementation for Scala.js)
}final class JUnit4(klass: Class[_]) extends BlockJUnit4ClassRunner(klass) {
// Default JUnit 4 runner
}class RunWith(value: Class[_ <: Runner]) extends StaticAnnotation with Annotation {
def annotationType(): Class[_ <: Annotation] = classOf[RunWith]
}Usage:
@RunWith(classOf[CustomTestRunner])
class SpecialTest {
@Test
def shouldRunWithCustomRunner(): Unit = {
// This test will be executed by CustomTestRunner
assertTrue(true)
}
}Parameterized Test Example:
@RunWith(classOf[ParameterizedRunner])
class ParameterizedTest(value: Int) {
@Test
def shouldTestWithParameter(): Unit = {
assertTrue(s"Value $value should be positive", value > 0)
}
}The JUnitFramework supports various command line arguments for test execution control:
val args = Array(
"-v", // Verbose output
"-n", // No color output
"-s", // Decode Scala names
"-a", // Log assert messages
"-c" // Don't log exception class names
)Argument Processing:
// Enable verbose mode
val verboseArgs = Array("-v")
val runner = framework.runner(verboseArgs, Array.empty, classLoader)
// Disable colored output for CI environments
val ciArgs = Array("-n")
val ciRunner = framework.runner(ciArgs, Array.empty, classLoader)case class RunSettings(
color: Boolean, // Enable colored output
decodeScalaNames: Boolean, // Decode Scala names in stack traces
verbose: Boolean, // Verbose logging
logAssert: Boolean, // Log assertion failures
notLogExceptionClass: Boolean // Don't log exception class names
)Usage in Custom Runners:
class CustomRunner(args: Array[String], remoteArgs: Array[String], settings: RunSettings) {
def executeTest(testMethod: Method): Unit = {
if (settings.verbose) {
println(s"Running test: ${settings.decodeName(testMethod.getName)}")
}
try {
testMethod.invoke(testInstance)
if (settings.verbose) {
println("Test passed")
}
} catch {
case e: AssertionError =>
if (settings.logAssert) {
println(s"Assertion failed: ${e.getMessage}")
}
throw e
}
}
}trait Task {
def taskDef(): TaskDef
def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task]
}
trait TaskDef {
def fullyQualifiedName(): String
def fingerprint(): Fingerprint
def explicitlySpecified(): Boolean
def selectors(): Array[Selector]
}Example Test Execution:
class JUnitTask(taskDef: TaskDef, settings: RunSettings) extends Task {
def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = {
val reporter = new Reporter(eventHandler, loggers, settings, taskDef)
try {
reporter.reportRunStarted()
val testClass = Class.forName(taskDef.fullyQualifiedName())
val testInstance = testClass.newInstance()
// Execute @Before methods
executeBeforeMethods(testInstance)
// Execute test methods
val testMethods = findTestMethods(testClass)
testMethods.foreach(executeTestMethod(testInstance, _, reporter))
// Execute @After methods
executeAfterMethods(testInstance)
reporter.reportRunFinished(failedCount, ignoredCount, totalCount, duration)
} catch {
case e: Exception =>
reporter.reportErrors("Test execution failed", None, 0.0, List(e))
}
Array.empty // No subtasks
}
}class JUnitEvent(
taskDef: TaskDef,
status: Status,
selector: Selector,
throwable: OptionalThrowable = new OptionalThrowable,
duration: Long = -1L
) extends Event {
def fullyQualifiedName(): String = taskDef.fullyQualifiedName()
def fingerprint(): Fingerprint = taskDef.fingerprint()
}Event Status Types:
Status.Success - Test passedStatus.Failure - Test failed with assertion errorStatus.Error - Test failed with unexpected exceptionStatus.Skipped - Test was ignored or assumption failedclass Reporter(
eventHandler: EventHandler,
loggers: Array[Logger],
settings: RunSettings,
taskDef: TaskDef
) {
def reportTestStarted(method: String): Unit
def reportTestFinished(method: String, succeeded: Boolean, timeInSeconds: Double): Unit
def reportErrors(prefix: String, method: Option[String], timeInSeconds: Double, errors: List[Throwable]): Unit
def reportIgnored(method: Option[String]): Unit
}class CustomParameterizedRunner(testClass: Class[_]) extends Runner {
private val parameters = getParameters(testClass)
def done(): String = ""
def tasks(taskDefs: Array[TaskDef]): Array[Task] = {
taskDefs.flatMap { taskDef =>
parameters.map { param =>
new ParameterizedTask(taskDef, param)
}
}
}
private def getParameters(clazz: Class[_]): Array[Any] = {
// Extract parameters from @Parameters annotation or method
Array(1, 2, 3, 4, 5) // Example parameters
}
}
class ParameterizedTask(taskDef: TaskDef, parameter: Any) extends Task {
def taskDef(): TaskDef = taskDef
def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = {
// Execute test with parameter
val testClass = Class.forName(taskDef.fullyQualifiedName())
val constructor = testClass.getConstructor(parameter.getClass)
val testInstance = constructor.newInstance(parameter)
// Execute test methods on parameterized instance
executeTestMethods(testInstance, eventHandler, loggers)
Array.empty
}
}// build.sbt
libraryDependencies += "org.scala-js" %%% "scalajs-junit-test-runtime" % "1.19.0" % Test
// Enable JUnit testing
testFrameworks += new TestFramework("org.scalajs.junit.JUnitFramework")// Configure test execution
Test / testOptions += Tests.Argument(
TestFrameworks.JUnit,
"-v", // Verbose
"-s", // Decode Scala names
"-a" // Log assertions
)class ParallelJUnitRunner extends Runner {
def tasks(taskDefs: Array[TaskDef]): Array[Task] = {
taskDefs.map(new ParallelTask(_))
}
}
class ParallelTask(taskDef: TaskDef) extends Task {
def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = {
// Execute test in parallel-safe manner
Future {
executeTestSafely(eventHandler, loggers)
}
Array.empty
}
}class FilteringRunner(filter: String => Boolean) extends Runner {
def tasks(taskDefs: Array[TaskDef]): Array[Task] = {
taskDefs
.filter(taskDef => filter(taskDef.fullyQualifiedName()))
.map(new FilteredTask(_))
}
}The runner framework provides flexibility for custom test execution strategies while maintaining compatibility with standard JUnit test discovery and reporting mechanisms.
Install with Tessl CLI
npx tessl i tessl/maven-org-scala-js--scalajs-junit-test-runtime