Comprehensive testing framework and specification library for Scala that enables behavior-driven development through executable specifications
Specs2 provides comprehensive test execution capabilities with flexible configuration options, multiple runners for different environments, and detailed control over test execution behavior.
Main execution utility for running specifications.
object Runner {
def execute(actions: Action[Unit], arguments: Arguments, exit: Boolean): Unit
def execute(specs: Seq[SpecificationStructure], arguments: Arguments, exit: Boolean): Unit
def run(specs: SpecificationStructure*): Unit
}Core execution engine that processes specification fragments using stream-based processing.
trait Executor {
def execute(env: Env): Process[Task, Fragment] => Process[Task, Fragment]
def execute1(env: Env): Process1[Fragment, Task[Fragment]]
}The executor is responsible for:
Default implementation of the Executor trait using scalaz-stream processing.
trait DefaultExecutor extends Executor {
def execute1(env: Env): Process1[Fragment, Task[Fragment]]
def sequencedExecution(env: Env): Process1[Fragment, Task[Fragment]]
def isolatedExecution(env: Env): Process1[Fragment, Task[Fragment]]
}Execution Strategies:
Execution environment that holds configuration and resources for test execution.
case class Env(
arguments: Arguments,
systemLogger: Logger,
printers: List[Printer] = Nil,
statisticsRepository: StatisticsRepository = StatisticsRepository.memory,
random: scala.util.Random = new scala.util.Random,
fileSystem: FileSystem = FileSystem,
executionContext: ExecutionContext = ExecutionContext.global
) {
def setArguments(args: Arguments): Env
def setPrinters(ps: List[Printer]): Env
def addPrinter(p: Printer): Env
}The environment provides:
Specs2 uses scalaz-stream Process[Task, Fragment] for fragment processing, enabling:
// Core execution pipeline
trait SpecificationExecution {
def executeFragments(env: Env): Process[Task, Fragment] => Process[Task, Fragment]
def executeBodies(env: Env): Process1[Fragment, Fragment]
def executeActions(env: Env): Process1[Fragment, Fragment]
}is method creates fragment streamtrait SpecificationStructure {
def fragments: Process[Task, Fragment]
def linked: Process[Task, Fragment]
def contents: Process[Task, Fragment]
}Fragment Ordering:
Usage Example:
// Run a single specification
Runner.run(new MySpec)
// Run multiple specifications
Runner.run(new UserSpec, new OrderSpec, new PaymentSpec)
// Run with custom arguments
Runner.execute(
List(new IntegrationSpec),
Arguments(sequential = true, stopOnFail = true),
exit = false
)Runs specifications from fully qualified class names.
class ClassRunner {
def run(className: String): Unit
def run(className: String, arguments: Arguments): Unit
def run(classNames: List[String], arguments: Arguments): Unit
}Usage:
# Command line execution
scala -cp "classpath" org.specs2.runner.ClassRunner com.example.UserSpec
# With arguments
scala -cp "classpath" org.specs2.runner.ClassRunner com.example.UserSpec -- sequentialDiscovers and runs specification files from filesystem.
class FilesRunner {
def run(path: String): Unit
def run(path: String, pattern: String): Unit
def run(paths: List[String], arguments: Arguments): Unit
}Usage:
# Run all specs in directory
scala -cp "classpath" org.specs2.runner.FilesRunner src/test/scala
# Run specs matching pattern
scala -cp "classpath" org.specs2.runner.FilesRunner src/test/scala ".*UserSpec.*"Integration with SBT test framework.
class SbtRunner extends TestFramework {
def name: String = "specs2"
def fingerprints: Array[Fingerprint]
def runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader): Runner2
}SBT Configuration:
// build.sbt
libraryDependencies += "org.specs2" %% "specs2-core" % "3.3.1" % "test"
testFrameworks += TestFrameworks.Specs2
// Run all tests
sbt test
// Run specific test
sbt "testOnly com.example.UserSpec"
// Run with arguments
sbt "testOnly * -- sequential stopOnFail"Integration with test framework notifiers for IDE support.
class NotifierRunner {
def run(specs: List[SpecificationStructure], notifier: EventHandler): Unit
def run(className: String, notifier: EventHandler): Unit
}Main configuration object controlling test execution behavior.
case class Arguments(
// Execution control
plan: Boolean = false,
skipAll: Boolean = false,
stopOnFail: Boolean = false,
sequential: Boolean = false,
isolated: Boolean = false,
threadsNb: Int = Runtime.getRuntime.availableProcessors,
// Filtering
include: String = "",
exclude: String = "",
// Output control
colors: Boolean = true,
noindent: Boolean = false,
showTimes: Boolean = false,
offset: Int = 0,
// Reporting
console: Boolean = true,
html: Boolean = false,
markdown: Boolean = false,
junitxml: Boolean = false,
// File paths
outdir: String = "target/specs2-reports",
srcdir: String = "src/test/scala"
)Command-line argument parsing for Arguments object.
trait ArgumentsArgs {
def parse(args: Array[String]): Arguments
def parseArguments(args: String*): Arguments
}Command Line Usage:
# Basic arguments
--sequential # Run examples sequentially
--stopOnFail # Stop on first failure
--plan # Show execution plan only
--skipAll # Skip all examples
# Filtering
--include "unit" # Include examples tagged with "unit"
--exclude "slow" # Exclude examples tagged with "slow"
# Output control
--colors # Enable colors (default)
--noColors # Disable colors
--showTimes # Show execution times
--offset 2 # Indent output by 2 spaces
# Reporting formats
--console # Console output (default)
--html # Generate HTML reports
--markdown # Generate Markdown reports
--junitxml # Generate JUnit XML reports
# File paths
--outdir target/reports # Output directory
--srcdir src/test/scala # Source directoryConvenient methods for creating common argument combinations.
trait ArgumentsShortcuts {
def sequential: Arguments
def isolated: Arguments
def stopOnFail: Arguments
def plan: Arguments
def skipAll: Arguments
def noColors: Arguments
def showTimes: Arguments
def html: Arguments
def markdown: Arguments
def junitxml: Arguments
}Usage in Specifications:
class ConfiguredSpec extends Specification { def is =
args(sequential, stopOnFail, showTimes) ^ s2"""
Configured specification
test 1 $test1
test 2 $test2
test 3 $test3
"""
}Execution lifecycle control and coordination.
trait Execute {
def execute[T](action: Action[T]): T
def execute[T](action: Action[T], timeout: FiniteDuration): T
def executeAll[T](actions: List[Action[T]]): List[T]
def executeSequentially[T](actions: List[Action[T]]): List[T]
def executeConcurrently[T](actions: List[Action[T]]): List[T]
}Monadic action type for composable operations with error handling.
case class Action[T](run: () => T) {
def map[S](f: T => S): Action[S]
def flatMap[S](f: T => Action[S]): Action[S]
def filter(p: T => Boolean): Action[T]
def recover(f: Throwable => T): Action[T]
def timeout(duration: FiniteDuration): Action[T]
}Usage Example:
val setupAction = Action { () =>
Database.createTables()
Database.seedTestData()
}
val testAction = Action { () =>
Database.query("SELECT * FROM users") must not(beEmpty)
}
val cleanupAction = Action { () =>
Database.cleanup()
}
val composedAction = for {
_ <- setupAction
result <- testAction
_ <- cleanupAction
} yield resultDiscovers specification classes dynamically.
trait SpecificationsFinder {
def find(pattern: String): List[String]
def find(classLoader: ClassLoader, pattern: String): List[String]
def findSpecifications(packages: List[String]): List[SpecificationStructure]
}Usage:
val finder = new SpecificationsFinder {}
val specClasses = finder.find("com.example.*Spec")
val specifications = finder.findSpecifications(List("com.example"))Automatic discovery patterns:
// File-based discovery
val fileSpecs = FilesRunner.findSpecifications("src/test/scala")
// Package-based discovery
val packageSpecs = ClassRunner.findSpecifications("com.example")
// Pattern-based discovery
val patternSpecs = SpecificationsFinder.find(".*IntegrationSpec.*")Run examples one after another:
class SequentialSpec extends Specification { def is = sequential ^ s2"""
Sequential execution (examples run in order)
setup step $setup
test operation $testOp
verify result $verify
cleanup step $cleanup
"""
}Run examples concurrently (default):
class ParallelSpec extends Specification { def is = s2"""
Parallel execution (examples run concurrently)
independent test 1 $test1
independent test 2 $test2
independent test 3 $test3
"""
}Run each example in isolation:
class IsolatedSpec extends Specification { def is = isolated ^ s2"""
Isolated execution (examples in separate classloaders)
test with global state 1 $testGlobal1
test with global state 2 $testGlobal2
"""
}Filter examples by tags:
# Include specific tags
sbt "testOnly * -- include unit,fast"
# Exclude specific tags
sbt "testOnly * -- exclude slow,integration"
# Complex filtering
sbt "testOnly * -- include unit exclude broken"Filter by specification or example names:
# Run specifications matching pattern
sbt "testOnly *UserSpec*"
# Run examples matching description
sbt "testOnly * -- ex create.*user"Skip examples based on conditions:
class ConditionalSpec extends Specification { def is = s2"""
Conditional execution
${if (System.getProperty("integration") == "true")
"integration test" ! integrationTest
else
"integration test (skipped)" ! skipped
}
unit test $unitTest
"""
}Stop execution on first failure:
class StopOnFailSpec extends Specification { def is =
args(stopOnFail = true) ^ s2"""
Stop on fail behavior
test 1 (will pass) $test1
test 2 (will fail) $test2
test 3 (won't run) $test3
"""Handle exceptions during execution:
class ExceptionHandlingSpec extends Specification { def is = s2"""
Exception handling
recoverable error $recoverableError
fatal error $fatalError
"""
def recoverableError = {
try {
riskyOperation()
success
} catch {
case _: RecoverableException =>
fallbackOperation() must beEqualTo(expected)
}
}
def fatalError = {
fatalOperation() must throwA[FatalException]
}
}Track execution performance:
# Show execution times
sbt "testOnly * -- showTimes"
# Show times with colors
sbt "testOnly * -- showTimes colors"Control concurrent execution:
// Limit concurrent threads
Arguments(threadsNb = 4)
// Use all available processors (default)
Arguments(threadsNb = Runtime.getRuntime.availableProcessors)Control memory usage during execution:
// Run with isolated classloaders (higher memory)
Arguments(isolated = true)
// Run sequentially (lower memory)
Arguments(sequential = true)CI-friendly configuration:
class CISpec extends Specification { def is =
args(
stopOnFail = true, // Fail fast
noColors = true, // No ANSI colors
junitxml = true, // Generate XML reports
html = false // Skip HTML for CI
) ^ s2"""
CI-optimized specification
critical test 1 $test1
critical test 2 $test2
"""IDE-friendly execution:
// IntelliJ IDEA runner support
@RunWith(classOf[JUnitRunner])
class IDESpec extends Specification { def is = s2"""
IDE-compatible specification
test 1 $test1
test 2 $test2
"""
}Maven integration:
<plugin>
<groupId>org.scalatest</groupId>
<artifactId>scalatest-maven-plugin</artifactId>
<configuration>
<reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
<junitxml>.</junitxml>
<filereports>WDF TestSuite.txt</filereports>
</configuration>
</plugin>stopOnFail judiciously and handle exceptions properlyInstall with Tessl CLI
npx tessl i tessl/maven-org-specs2--specs2-2-10