Comprehensive testing framework and specification library for Scala that enables behavior-driven development through executable specifications
Specs2 provides a flexible reporting system with multiple output formats including console, HTML, Markdown, and JUnit XML. The reporting system is designed to generate both human-readable documentation and machine-parseable results for continuous integration.
Base interface for all reporting functionality with lifecycle management.
trait Reporter {
def prepare(env: Env, printers: List[Printer]): List[SpecificationStructure] => Action[Unit]
def report(env: Env, printers: List[Printer]): SpecStructure => Action[Unit]
def finalize(env: Env, printers: List[Printer]): List[SpecificationStructure] => Action[Unit]
}The Reporter coordinates the complete reporting lifecycle:
Base interface for output formatting with fold-based processing.
trait Printer {
def prepare(env: Env, specifications: List[SpecificationStructure]): Action[Unit]
def finalize(env: Env, specifications: List[SpecificationStructure]): Action[Unit]
def fold(env: Env, spec: SpecStructure): Fold[Fragment]
}The Printer uses a fold-based approach where:
trait Fold[T] {
def start: T
def fold(previous: T, fragment: Fragment): T
def end(final: T): T
}This enables:
Formats output for console display with colors and formatting.
trait TextPrinter extends Printer {
def printText(text: String): Action[Unit]
def printExample(example: Example): Action[Unit]
def printResult(result: Result): Action[Unit]
def printStatistics(stats: Statistics): Action[Unit]
}Console Output Example:
Calculator specification
Calculator should
+ add two numbers correctly
+ subtract two numbers correctly
x handle division by zero
'10 / 0' doesn't throw a java.lang.ArithmeticException
Total for specification Calculator:
Finished in 23 ms
3 examples, 1 failure, 0 errortrait AnsiColors {
def green(text: String): String
def red(text: String): String
def blue(text: String): String
def yellow(text: String): String
def cyan(text: String): String
def white(text: String): String
def bold(text: String): String
}Usage:
// Enable/disable colors
Arguments(colors = true) // Enable colors (default)
Arguments(colors = false) // Disable colors
// Command line
sbt "testOnly * -- colors" # Enable colors
sbt "testOnly * -- noColors" # Disable colorsGenerates complete HTML pages for specifications.
trait HtmlTemplate {
def page(spec: SpecificationStructure, fragments: Fragments): NodeSeq
def head(spec: SpecificationStructure): NodeSeq
def body(spec: SpecificationStructure, fragments: Fragments): NodeSeq
def css: String
def javascript: String
}Individual specification HTML page generation.
class SpecHtmlPage {
def generate(spec: SpecificationStructure): Action[Unit]
def generateToFile(spec: SpecurationStructure, file: File): Action[Unit]
def toXml(spec: SpecificationStructure): NodeSeq
}HTML Generation:
# Generate HTML reports
sbt "testOnly * -- html"
# Specify output directory
sbt "testOnly * -- html outdir target/html-reports"
# Generate with custom CSS
sbt "testOnly * -- html css custom-styles.css"Generated HTML reports include:
HTML utilities and transformations.
object Htmlx {
def render(nodes: NodeSeq): String
def toXhtml(html: String): NodeSeq
def format(nodes: NodeSeq): NodeSeq
def addCss(css: String): NodeSeq => NodeSeq
def addJavascript(js: String): NodeSeq => NodeSeq
}Navigation structure generation for multi-specification reports.
trait TableOfContents {
def create(specs: List[SpecificationStructure]): NodeSeq
def createEntry(spec: SpecificationStructure): NodeSeq
def createSection(title: String, specs: List[SpecificationStructure]): NodeSeq
}Cross-reference and linking between specification pages.
trait Indexing {
def createIndex(specs: List[SpecificationStructure]): NodeSeq
def createLinks(spec: SpecificationStructure): Map[String, String]
def resolveReferences(html: NodeSeq): NodeSeq
}Multi-Specification HTML Report:
// Generate linked HTML reports for multiple specifications
val specs = List(new UserSpec, new OrderSpec, new PaymentSpec)
HtmlReporter.generateSuite(specs, "target/html-reports")Generates Markdown documentation from specifications.
trait MarkdownPrinter extends Printer {
def printMarkdown(fragments: Fragments): Action[Unit]
def printTitle(title: String): Action[Unit]
def printSection(section: String): Action[Unit]
def printCodeBlock(code: String): Action[Unit]
def printTable(headers: List[String], rows: List[List[String]]): Action[Unit]
}Markdown Generation:
# Generate Markdown reports
sbt "testOnly * -- markdown"
# Specify output file
sbt "testOnly * -- markdown outfile README.md"Generated Markdown Example:
# Calculator Specification
Calculator should
- ✓ add two numbers correctly
- ✓ subtract two numbers correctly
- ✗ handle division by zero'10 / 0' doesn't throw a java.lang.ArithmeticException
## Statistics
- **Total**: 3 examples
- **Passed**: 2
- **Failed**: 1
- **Errors**: 0
- **Execution time**: 23msGenerates JUnit-compatible XML reports for CI integration.
trait JUnitXmlPrinter extends Printer {
def printXml(specs: List[SpecificationStructure]): Action[Unit]
def printTestSuite(spec: SpecificationStructure): NodeSeq
def printTestCase(example: Example): NodeSeq
def printFailure(failure: Result): NodeSeq
def printError(error: Result): NodeSeq
}JUnit XML Generation:
# Generate JUnit XML reports
sbt "testOnly * -- junitxml"
# Specify output directory
sbt "testOnly * -- junitxml outdir target/test-reports"Generated XML Structure:
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="CalculatorSpec" tests="3" failures="1" errors="0" time="0.023">
<testcase name="add two numbers correctly" classname="CalculatorSpec" time="0.005"/>
<testcase name="subtract two numbers correctly" classname="CalculatorSpec" time="0.008"/>
<testcase name="handle division by zero" classname="CalculatorSpec" time="0.010">
<failure message="Expected exception not thrown" type="assertion">
'10 / 0' doesn't throw a java.lang.ArithmeticException
</failure>
</testcase>
</testsuite>SBT-specific output formatting for build tool integration.
trait SbtPrinter extends Printer {
def printSbtResult(result: Result, testName: String): Action[Unit]
def printSbtSummary(stats: Statistics): Action[Unit]
}Integration with test framework notifiers for IDE support.
trait NotifierPrinter extends Printer {
def notifyStart(testName: String): Action[Unit]
def notifyEnd(testName: String, result: Result): Action[Unit]
def notifyError(testName: String, error: Throwable): Action[Unit]
}Create custom output formats:
trait CustomPrinter extends Printer {
def format(fragments: Fragments): String
def writeToFile(content: String, file: File): Action[Unit]
}Example Custom Printer:
class JsonPrinter extends CustomPrinter {
def print(fragments: Fragments): Action[Unit] = {
val json = fragments.fragments.map {
case Example(desc, result) =>
s"""{"description":"$desc","status":"${result.status}"}"""
case Text(text) =>
s"""{"type":"text","content":"$text"}"""
}.mkString("[", ",", "]")
writeToFile(json, new File("target/results.json"))
}
}Structured data validation and reporting with tabular output.
case class Form(
title: String = "",
rows: List[Row] = Nil
) {
def tr(row: Row): Form
def th(cells: Cell*): Form
def td(cells: Cell*): Form
}Individual form fields with validation.
case class Field[T](
label: String,
value: T,
expected: Option[T] = None
) {
def apply(actual: T): Field[T]
def must(matcher: Matcher[T]): Field[T]
}Form Usage Example:
class FormReportSpec extends Specification { def is = s2"""
User validation form
$userValidationForm
"""
def userValidationForm = Form("User Validation").
tr(Field("Name", user.name).must(not(beEmpty))).
tr(Field("Email", user.email).must(beMatching(emailRegex))).
tr(Field("Age", user.age).must(beGreaterThan(0)))
}Card-based reporting layout for structured data presentation.
trait Cards {
def card(title: String): Card
def toTabs: Tabs
}
case class Card(
title: String,
body: Fragments
) {
def show: Fragment
}Tabbed interface for organizing related content.
trait Tabs {
def tab(title: String, content: Fragments): Tab
def show: Fragment
}
case class Tab(
title: String,
content: Fragments,
active: Boolean = false
) {
def activate: Tab
}Execution statistics collection and reporting.
case class Statistics(
examples: Int = 0,
successes: Int = 0,
failures: Int = 0,
errors: Int = 0,
pending: Int = 0,
skipped: Int = 0,
trend: Option[Statistics] = None
) {
def total: Int = examples
def hasFailures: Boolean = failures > 0
def hasErrors: Boolean = errors > 0
def isSuccess: Boolean = !hasFailures && !hasErrors
}Repository for persisting and retrieving execution statistics across test runs.
trait StatisticsRepository {
def store(spec: SpecificationStructure, stats: Statistics): Action[Unit]
def retrieve(spec: SpecificationStructure): Action[Option[Statistics]]
def clear(spec: SpecificationStructure): Action[Unit]
}
object StatisticsRepository {
def memory: StatisticsRepository
def file(path: String): StatisticsRepository
}Usage:
Execution timing and performance metrics.
case class ExecutionTime(
duration: Duration,
timestamp: Long = System.currentTimeMillis
) {
def formatted: String
def inMillis: Long
def inSeconds: Double
}Enable Timing:
# Show execution times in console
sbt "testOnly * -- showTimes"
# Include timing in HTML reports
sbt "testOnly * -- html showTimes"# Set output directory for all reports
sbt "testOnly * -- outdir target/custom-reports"
# HTML reports in specific directory
sbt "testOnly * -- html outdir target/html"
# Multiple formats with same base directory
sbt "testOnly * -- html markdown junitxml outdir target/reports"Customize HTML report appearance:
// Custom CSS
Arguments(html = true, css = "custom-styles.css")
// Custom JavaScript
Arguments(html = true, javascript = "custom-behavior.js")
// Custom template
Arguments(html = true, template = "custom-template.html")# GitHub Actions example
- name: Run tests with reports
run: sbt "test -- junitxml html outdir target/test-reports"
- name: Publish test results
uses: dorny/test-reporter@v1
with:
name: Specs2 Tests
path: target/test-reports/*.xml
reporter: java-junitclass DocumentationSpec extends Specification { def is =
args(html = true, markdown = true) ^ s2"""
# API Documentation
This specification serves as both tests and documentation.
## User Management API
The user management system provides the following capabilities:
### User Creation
create valid user $createValidUser
validate required fields $validateRequired
handle duplicate emails $handleDuplicates
### User Authentication
authenticate with valid credentials $validAuth
reject invalid credentials $invalidAuth
"""Install with Tessl CLI
npx tessl i tessl/maven-org-specs2--specs2-2-10