Scala.js version of the sbt testing interface that provides a standardized API for test frameworks to integrate with SBT and run tests in a Scala.js (JavaScript) environment
—
Test discovery system using fingerprints to identify test classes through annotations or inheritance patterns, enabling automatic detection of test suites during SBT's discovery phase.
Base interface for test class identification patterns used during discovery.
/**
* A way to identify test classes and/or modules that should be discovered.
* Implementations may not rely on identity of Fingerprints since they are serialized between JS/JVM.
*/
trait FingerprintIdentifies test classes or modules based on the presence of specific annotations.
/**
* Indicates classes or modules with a specific annotation should be discovered as test classes.
* The annotation can be on top-level methods or on the class/module itself.
*/
trait AnnotatedFingerprint extends Fingerprint {
/**
* Indicates whether modules with the annotation should be considered during discovery.
* If a framework allows both classes and modules, return two different fingerprints:
* one with isModule() = false and another with isModule() = true.
* @return true for modules (singleton objects), false for classes
*/
def isModule(): Boolean
/**
* The fully qualified name of the annotation that identifies test classes/modules.
* @return annotation name for test identification
*/
def annotationName(): String
}Usage Examples:
// Discover classes with @Test annotation
val classAnnotationFingerprint = new AnnotatedFingerprint {
def isModule() = false
def annotationName() = "org.junit.Test"
}
// Discover objects with @TestSuite annotation
val objectAnnotationFingerprint = new AnnotatedFingerprint {
def isModule() = true
def annotationName() = "com.example.TestSuite"
}
// Framework returning both fingerprints
class MyFramework extends Framework {
def fingerprints(): Array[Fingerprint] = Array(
classAnnotationFingerprint,
objectAnnotationFingerprint
)
}Identifies test classes or modules based on inheritance from specific superclasses or traits.
/**
* Indicates classes (and possibly modules) that extend a particular superclass
* or mix in a particular supertrait should be discovered as test classes.
*/
trait SubclassFingerprint extends Fingerprint {
/**
* Indicates whether modules (singleton objects) that extend the superclass/supertrait
* should be considered during discovery.
* Returning false speeds up discovery by quickly bypassing module classes.
* @return true to discover modules, false for classes only
*/
def isModule(): Boolean
/**
* The name of the superclass or supertrait that identifies test classes.
* @return superclass/supertrait name for test identification
*/
def superclassName(): String
/**
* Indicates whether discovered classes must have a no-arg constructor.
* If true, client should not discover subclasses without no-arg constructors.
* @return true if no-arg constructor required
*/
def requireNoArgConstructor(): Boolean
}Usage Examples:
// Discover classes extending TestSuite (with no-arg constructor)
val suiteFingerprint = new SubclassFingerprint {
def isModule() = false
def superclassName() = "com.example.TestSuite"
def requireNoArgConstructor() = true
}
// Discover objects extending SpecBase (no constructor requirement for objects)
val specFingerprint = new SubclassFingerprint {
def isModule() = true
def superclassName() = "com.example.SpecBase"
def requireNoArgConstructor() = false
}
// Discover both classes and objects extending BaseTest
val flexibleFingerprint = new SubclassFingerprint {
def isModule() = false // or true for separate fingerprint
def superclassName() = "com.example.BaseTest"
def requireNoArgConstructor() = false
}Test frameworks register their discovery patterns by returning fingerprints from the fingerprints() method:
class MyTestFramework extends Framework {
def fingerprints(): Array[Fingerprint] = Array(
// JUnit-style annotation discovery
new AnnotatedFingerprint {
def isModule() = false
def annotationName() = "org.junit.Test"
},
// ScalaTest-style inheritance discovery
new SubclassFingerprint {
def isModule() = false
def superclassName() = "org.scalatest.Suite"
def requireNoArgConstructor() = true
},
// Specs2-style object discovery
new SubclassFingerprint {
def isModule() = true
def superclassName() = "org.specs2.Specification"
def requireNoArgConstructor() = false
}
)
}SBT uses fingerprints to scan the classpath and identify test classes:
AnnotatedFingerprint, check for specified annotationsSubclassFingerprint, check superclass hierarchyrequireNoArgConstructor() is true, verify no-arg constructor existsTaskDef instances for discovered test classesTaskDef array to Runner.tasks() for task creationFrameworks can register multiple fingerprints to support different discovery patterns:
def fingerprints(): Array[Fingerprint] = Array(
// Method-level annotations
new AnnotatedFingerprint {
def isModule() = false
def annotationName() = "com.example.Test"
},
// Class-level annotations
new AnnotatedFingerprint {
def isModule() = false
def annotationName() = "com.example.TestClass"
},
// Inheritance-based discovery
new SubclassFingerprint {
def isModule() = false
def superclassName() = "com.example.BaseTest"
def requireNoArgConstructor() = true
},
// Object-based specs
new SubclassFingerprint {
def isModule() = true
def superclassName() = "com.example.Spec"
def requireNoArgConstructor() = false // Objects don't have constructors
}
)Performance Optimization:
isModule() = false for class-only frameworks to skip object scanningrequireNoArgConstructor() = true to filter classes earlysuperclassName() and annotationName() as specific as possibleFlexibility:
Framework Compatibility:
// Comprehensive discovery for flexible framework
class FlexibleFramework extends Framework {
def fingerprints(): Array[Fingerprint] = Array(
// JUnit compatibility
new AnnotatedFingerprint {
def isModule() = false
def annotationName() = "org.junit.Test"
},
// Custom annotation support
new AnnotatedFingerprint {
def isModule() = false
def annotationName() = "com.myframework.Test"
},
// Suite-based classes
new SubclassFingerprint {
def isModule() = false
def superclassName() = "com.myframework.TestSuite"
def requireNoArgConstructor() = true
},
// Spec-based objects
new SubclassFingerprint {
def isModule() = true
def superclassName() = "com.myframework.Spec"
def requireNoArgConstructor() = false
}
)
}Discovery typically handles errors gracefully by skipping problematic classes:
// Framework should handle discovery errors internally
def fingerprints(): Array[Fingerprint] = {
try {
Array(
createAnnotationFingerprint(),
createSubclassFingerprint()
)
} catch {
case _: ClassNotFoundException =>
// Fallback to basic discovery
Array(createBasicFingerprint())
}
}Fingerprints should validate their configuration:
class MyAnnotatedFingerprint(annotation: String) extends AnnotatedFingerprint {
require(annotation != null && annotation.nonEmpty, "Annotation name cannot be empty")
def annotationName() = annotation
def isModule() = false
}Install with Tessl CLI
npx tessl i tessl/maven-org-scala-js--scalajs-test-interface