Mutable specifications provide an imperative-style API for writing tests where examples are defined using side-effects and thrown expectations. This style is familiar to developers coming from traditional unit testing frameworks like JUnit or ScalaTest.
Main mutable specification class using thrown expectations.
// org.specs2.mutable
abstract class Specification extends SpecificationLikeUsage Example:
import org.specs2.mutable._
class CalculatorSpec extends Specification {
"Calculator" should {
"add two numbers" in {
val calc = new Calculator
calc.add(2, 3) must beEqualTo(5)
}
"subtract two numbers" in {
val calc = new Calculator
calc.subtract(5, 2) must beEqualTo(3)
}
"handle division by zero" in {
val calc = new Calculator
calc.divide(1, 0) must throwA[ArithmeticException]
}
}
}Core mutable specification trait.
trait SpecificationLike extends SpecificationStructure
with SpecificationCreation
with SpecificationFeaturesProvides mutable-specific implementations of:
Lightweight mutable specification class.
abstract class Spec extends SpecLikeUsage Example:
import org.specs2.mutable._
class SimpleSpec extends Spec {
"Simple test" should {
"pass" in {
1 + 1 must beEqualTo(2)
}
}
}Lightweight mutable specification trait.
trait SpecLike extends SpecificationStructure
with ExampleDsl0
with MustThrownMatchers1Includes:
ExampleDsl0: Basic example creation methodsMustThrownMatchers1: Core matchers with thrown expectationsDSL for creating examples in mutable specifications.
trait ExampleDsl {
def in[T: AsResult](body: => T): Unit
def should[T: AsResult](body: => T): Unit
def can[T: AsResult](body: => T): Unit
def >>[T: AsResult](body: => T): Unit
}Adding descriptive text to mutable specifications.
trait TextDsl {
def br: Unit
def p: Unit
def tab: Unit
def backtab: Unit
def end: Unit
}Building specification structure imperatively.
trait MutableFragmentBuilder {
def addFragment(fragment: Fragment): Unit
def addText(text: String): Unit
def addExample(description: String, body: => Any): Unit
def addStep(action: => Any): Unit
}Matchers that throw exceptions on failure (for mutable specs).
trait MustThrownMatchers extends MustMatchers {
override def createExpectation[T](t: => T): Expectable[T]
override def checkFailure[T](m: MatchResult[T]): MatchResult[T]
}Alternative "should" syntax with thrown expectations.
trait ShouldThrownMatchers extends ShouldMatchers {
override def createExpectation[T](t: => T): Expectable[T]
override def checkFailure[T](m: MatchResult[T]): MatchResult[T]
}Mutable specification structure management.
trait SpecificationStructure {
protected var specFragments: Vector[Fragment] = Vector.empty
def addFragment(f: Fragment): Unit
def prependFragment(f: Fragment): Unit
def content: Fragments
}Enhanced mutable structure with modification methods.
trait MutableSpecificationStructure extends SpecificationStructure {
def insertFragment(index: Int, f: Fragment): Unit
def removeFragment(index: Int): Unit
def replaceFragment(index: Int, f: Fragment): Unit
def clearFragments(): Unit
}Setup actions before each example.
trait BeforeEach {
def before: Any
def apply[T: AsResult](a: => T): Result = {
before
AsResult(a)
}
}Cleanup actions after each example.
trait AfterEach {
def after: Any
def apply[T: AsResult](a: => T): Result = {
try AsResult(a)
finally after
}
}Wrap each example with custom logic.
trait AroundEach {
def around[T: AsResult](t: => T): Result
def apply[T: AsResult](a: => T): Result = around(a)
}Combined setup and cleanup.
trait BeforeAfterEach extends BeforeEach with AfterEach {
def apply[T: AsResult](a: => T): Result = {
before
try AsResult(a)
finally after
}
}Nested specification structure:
class DatabaseSpec extends Specification {
"Database operations" should {
"User operations" should {
"create users" in { /* test */ }
"update users" in { /* test */ }
"delete users" in { /* test */ }
}
"Order operations" should {
"create orders" in { /* test */ }
"process orders" in { /* test */ }
}
}
}Configure mutable specification behavior:
trait ArgumentsShortcuts {
def sequential: Arguments
def isolated: Arguments
def stopOnFail: Arguments
def plan: Arguments
def skipAll: Arguments
}Organizing and filtering examples:
trait TagDsl {
def tag(names: String*): Unit
def section(name: String): Unit
}Usage:
class TaggedSpec extends Specification {
"Feature X" should {
"work in normal cases" in {
// test code
} tag("unit", "fast")
"work under load" in {
// test code
} tag("integration", "slow")
}
}import org.specs2.mutable._
class UserServiceSpec extends Specification {
"UserService" should {
val service = new UserService
"create new users" in {
val user = service.create("john", "john@test.com")
user.name must beEqualTo("john")
user.email must beEqualTo("john@test.com")
}
"find users by email" in {
service.create("jane", "jane@test.com")
val found = service.findByEmail("jane@test.com")
found must beSome.which(_.name == "jane")
}
}
}class DatabaseSpec extends Specification with BeforeAfterEach {
def before = {
Database.createTables()
Database.seedTestData()
}
def after = {
Database.cleanup()
}
"Database operations" should {
"insert records" in {
val count = Database.insert(testRecord)
count must beEqualTo(1)
}
"query records" in {
val results = Database.query("SELECT * FROM users")
results must not(beEmpty)
}
}
}trait UserBehavior {
def validUser = {
"have a valid name" in {
user.name must not(beEmpty)
}
"have a valid email" in {
user.email must beMatching(emailRegex)
}
}
def user: User
def emailRegex: String
}
class UserSpec extends Specification with UserBehavior {
def user = User("john", "john@test.com")
def emailRegex = ".*@.*\\..*"
"User" should {
validUser
"calculate age correctly" in {
user.copy(birthYear = 1990).age must beGreaterThan(30)
}
}
}class ConditionalSpec extends Specification {
"Feature" should {
if (System.getProperty("integration.tests") == "true") {
"work with external service" in {
// integration test code
}
}
"work offline" in {
// unit test code
}
}
}