No seriously, keep it simple.
simplespec is a thin Scala wrapper over JUnit, the most commonly-used test framework on the JVM. simplespec was originally written by Coda Hale and is now maintained and developed by Simple. The library features extensible Hamcrest matchers, easy mocks, and other niceties.
- Scala 2.9.2
- JUnit 4.10.x
- Mockito 1.9.x
First, specify simplespec as a dependency:
<dependencies>
<dependency>
<groupId>com.simple</groupId>
<artifactId>simplespec_2.9.2</artifactId>
<version>0.7.0</version>
</dependency>
</dependencies>
Second, write a spec:
import com.example.Stack
import org.junit.Test
import com.simple.simplespec.Spec
class StackSpec extends Spec {
class `An empty stack` {
val stack = Stack()
@Test def `has a size of zero` = {
stack.size.must(be(0))
}
@Test def `is empty` = {
stack.isEmpty.must(be(true))
}
class `with an item added to it` {
stack += "woo"
@Test def `might have an item in it` = {
stack.must(be(empty))
}
}
}
}
The execution model for a Spec
is just a logical extension of how JUnit itself
works -- a Spec
class contains one or more regular classes, each of which can
contain zero or more @Test
-annotated methods or further nested classes.
When JUnit runs the Spec
class, it creates new instances of each class for
each test method run, allowing for full test isolation. In the above example,
first an instance of StackSpec
would be created, then an instance of
StackSpec#`An empty stack`
, then an instance of
StackSpec#`An empty stack`#`with an item added to it`
, and finally its
`might have an item in it`
method is run as a test.
The tradeoff of this execution model (vs. one which shares state between test invocation) is that tests which create a substantial amount of shared state (e.g., data-intensive tests) spend a lot of time setting up or tearing down state.
Unlike JUnit, simplespec doesn't require your test methods to return void.
The outer Spec
instance has beforeEach
and afterEach
methods which can be
overridden to perform setup and teardown tasks for each test contained in the
context. simplespec also provides BeforeEach
, AfterEach
, and
BeforeAndAfterEach
traits which inner classes can extend to perform more
tightly-scoped setup and teardown tasks.
simplespec provides a thin layer over Hamcrest matchers to allow for declarative assertions in your tests:
stack.must(be(empty))
simplespec includes the following matchers by default, but you're encouraged to write your own:
x.must(equal(y))
: Assertsx == y
.x.must(be(y))
: A synonym forequal
.x.must(beA(klass))
: Asserts thatx
is assignable as an instance ofklass
.x.must(be(matcher))
: Asserts thatmatcher
applies tox
.x.must(not(be(matcher)))
: Asserts thatmatcher
does not apply tox
.x.must(be(empty))
: Asserts thatx
is aTraversableLike
which is empty.x.must(haveSize(n))
: Asserts thatx
is aTraversableLike
which hasn
elements.x.must(contain(y))
: Asserts thatx
is aSeqLike
which contains the elementy
.x.must(be(notNull))
: Asserts thatx
is notnull
.x.must(be(approximately(y, delta)))
: Asserts thatx
is withindelta
ofy
. Useful for floating-point math.x.must(be(lessThan(2))
: Asserts thatx
is less than2
.x.must(be(greaterThan(2))
: Asserts thatx
is greater than2
.x.must(be(lessThanOrEqualTo(2))
: Asserts thatx
is less than or equal to2
.x.must(be(greaterThanOrEqualTo(2))
: Asserts thatx
is greater than or equal to2
.x.must(startWith("woo"))
: Asserts that stringx
starts with"woo"
.x.must(endWith("woo"))
: Asserts that stringx
ends with"woo"
.x.must(contain("woo"))
: Asserts that stringx
contains with"woo"
.x.must(`match`(".*oo".r))
: Asserts that stringx
matches the regular expression.*oo
.
Matchers like be
and not
take matchers as their arguments, which means you
can write domain-specific matchers for your tests:
class IsSufficientlyCromulentMatcher extends BaseMatcher[Fromulator] {
def describeTo(description: Description) {
description.appendText("a cromulemnt fromulator")
}
def matches(item: AnyRef) = item match {
case fromulator: Fromulator => fromulator.isCromulent
case _ => false
}
}
trait CromulentMatcher {
def cromulent = new IsSufficientlyCromulentMatcher
}
class BlahBlahSpec extends Spec with CromulentMatcher {
class `A Fromulator` {
val fromulator = new Fromulator
def `is cromulent` = {
fromulator.must(be(cromulent)
}
}
}
simplespec also includes two helper methods: evaluating
and eventually
.
evaluating
captures a closure and allows you to make assertions about what
happens when it's evaluated:
@Test def `throws an exception` = {
evaluating {
dooHicky.stop()
}.must(throwAn[UnsupportedOperationException])
}
eventually
also captures a closure, but allows you to assert things about
what happens when the closure is evaluated which might not be true the first
few times:
@Test def `decay to zero` = {
eventually {
thingy.rate
}.must(be(approximately(0.0, 0.001)))
}
See Matchers.scala
for the full run-down.
Also, yeah, mocks. simplespec uses Mockito for its mocking stuff:
class PublisherSpec extends Spec {
class `A publisher` {
val message = mock[Message]
val queue = mock[Queue]
queue.enqueue(any).returns(0, 1, 2, 3)
val publisher = new Publisher(queue)
@Test def `sends a message to the queue` = {
publisher.receive(message)
verify.one(queue).enqueue(message)
}
}
}
See Mocks.scala
for the full run-down.
Copyright (c) 2010-2012 Coda Hale
Copyright (c) 2012 Simple Finance Technology
Published under The MIT License, see LICENSE