No seriously, keep it simple.
Simplespec is a thin layer of convenience over JUnit, the most commonly-used test framework on the JVM.
- Scala 2.9.1 or 2.9.0-1 or 2.8.1
- JUnit 4.8.x
- Mockito 1.8.x
First, specify SimpleSpec as a dependency:
<repositories>
<repository>
<id>repo.codahale.com</id>
<url>http://repo.codahale.com</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.codahale</groupId>
<artifactId>simplespec_${scala.version}</artifactId>
<version>0.5.2</version>
</dependency>
</dependencies>Second, write a spec:
import com.example.Stack
import org.junit.Test
import com.codahale.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 thatxis assignable as an instance ofklass.x.must(be(matcher)): Asserts thatmatcherapplies tox.x.must(not(be(matcher))): Asserts thatmatcherdoes not apply tox.x.must(be(empty)): Asserts thatxis aTraversableLikewhich is empty.x.must(haveSize(n)): Asserts thatxis aTraversableLikewhich hasnelements.x.must(contain(y)): Asserts thatxis aSeqLikewhich contains the elementy.x.must(be(notNull)): Asserts thatxis notnull.x.must(be(approximately(y, delta))): Asserts thatxis withindeltaofy. Useful for floating-point math.x.must(be(lessThan(2)): Asserts thatxis less than2.x.must(be(greaterThan(2)): Asserts thatxis greater than2.x.must(be(lessThanOrEqualTo(2)): Asserts thatxis less than or equal to2.x.must(be(greaterThanOrEqualTo(2)): Asserts thatxis greater than or equal to2.x.must(startWith("woo")): Asserts that stringxstarts with"woo".x.must(endWith("woo")): Asserts that stringxends with"woo".x.must(contain("woo")): Asserts that stringxcontains with"woo".x.must(`match`(".*oo".r)): Asserts that stringxmatches 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-2011 Coda Hale
Published under The MIT License, see LICENSE