web/specs.textile (222 lines of code) (raw):
---
prev: coll2.textile
next: concurrency.textile
title: Testing with specs
layout: post
---
This lesson covers testing with Specs, a Behavior-Driven Design (BDD) Framework for Scala.
* "extends Specification":#example
** nested examples
* "Execution Model":#scope
* "Setup and TearDown":#setup
** doFirst
** doBefore
** doAfter
* "Matchers":#matchers
** mustEqual
** contains
** sameSize?
** Write your own
* "Mocks":#mocks
* "Spies":#spies
* "run in sbt":#sbt
h2(#example). extends Specification
Let's just jump in.
<pre>
import org.specs._
object ArithmeticSpec extends Specification {
"Arithmetic" should {
"add two numbers" in {
1 + 1 mustEqual 2
}
"add three numbers" in {
1 + 1 + 1 mustEqual 3
}
}
}
</pre>
*Arithmetic* is the *System Under Specification*
*add* is a context.
*add two numbers* and *add three numbers* are examples.
@mustEqual@ indicates an *expectation*
@1 mustEqual 1@ is a common placeholder *expectation* before you start writing real tests. All examples should have at least one expectation.
h3. Duplication
Notice how two tests both have @add@ in their name? We can get rid of that by *nesting* expectations.
<pre>
import org.specs._
object ArithmeticSpec extends Specification {
"Arithmetic" should {
"add" in {
"two numbers" in {
1 + 1 mustEqual 2
}
"three numbers" in {
1 + 1 + 1 mustEqual 3
}
}
}
}
</pre>
h2(#scope). Execution Model
<pre>
object ExecSpec extends Specification {
"Mutations are isolated" should {
var x = 0
"x equals 1 if we set it." in {
x = 1
x mustEqual 1
}
"x is the default value if we don't change it" in {
x mustEqual 0
}
}
}
</pre>
h2(#setup). Setup, Teardown
h3. doBefore & doAfter
<pre>
"my system" should {
doBefore { resetTheSystem() /** user-defined reset function */ }
"mess up the system" in {...}
"and again" in {...}
doAfter { cleanThingsUp() }
}
</pre>
*NOTE* @doBefore@/@doAfter@ are only run on leaf examples.
h3. doFirst & doLast
@doFirst@/@doLast@ is for single-time setup. (need example, I don't use this)
<pre>
"Foo" should {
doFirst { openTheCurtains() }
"test stateless methods" in {...}
"test other stateless methods" in {...}
doLast { closeTheCurtains() }
}
</pre>
h2(#matchers). Matchers
You have data, you want to make sure it's right. Let's tour the most commonly used matchers. (See Also "Matchers Guide":https://code.google.com/p/specs/wiki/MatchersGuide)
h3. mustEqual
We've seen several examples of mustEqual already.
<pre>
1 mustEqual 1
"a" mustEqual "a"
</pre>
Reference equality, value equality.
h3. elements in a Sequence
<pre>
val numbers = List(1, 2, 3)
numbers must contain(1)
numbers must not contain(4)
numbers must containAll(List(1, 2, 3))
numbers must containInOrder(List(1, 2, 3))
List(1, List(2, 3, List(4)), 5) must haveTheSameElementsAs(List(5, List(List(4), 2, 3), 1))
</pre>
h3. Items in a Map
<pre>
map must haveKey(k)
map must notHaveKey(k)
map must haveValue(v)
map must notHaveValue(v)
</pre>
h3. Numbers
<pre>
a must beGreaterThan(b)
a must beGreaterThanOrEqualTo(b)
a must beLessThan(b)
a must beLessThanOrEqualTo(b)
a must beCloseTo(b, delta)
</pre>
h3. Options
<pre>
a must beNone
a must beSome[Type]
a must beSomething
a must beSome(value)
</pre>
h3. throwA
<pre>
a must throwA[WhateverException]
</pre>
This is shorter than a try catch with a fail in the body.
You can also expect a specific message
<pre>
a must throwA(WhateverException("message"))
</pre>
You can also match on the exception:
<pre>
a must throwA(new Exception) like {
case Exception(m) => m.startsWith("bad")
}
</pre>
h3. Write your own Matchers
<pre>
import org.specs.matcher.Matcher
</pre>
h4. As a val
<pre>
"A matcher" should {
"be created as a val" in {
val beEven = new Matcher[Int] {
def apply(n: => Int) = {
(n % 2 == 0, "%d is even".format(n), "%d is odd".format(n))
}
}
2 must beEven
}
}
</pre>
The contract is to return a tuple containing whether the expectation is true, and a message for when it is and isn't true.
h4. As a case class
<pre>
case class beEven(b: Int) extends Matcher[Int]() {
def apply(n: => Int) = (n % 2 == 0, "%d is even".format(n), "%d is odd".format(n))
}
</pre>
Using a case class makes it more shareable.
h2(#mocks). Mocks
<pre>
import org.specs.Specification
import org.specs.mock.Mockito
abstract class Foo[T] {
def get(i: Int): T
}
object MockExampleSpec extends Specification with Mockito {
val m = mock[Foo[String]]
m.get(0) returns "one"
m.get(0)
there was one(m).get(0)
there was no(m).get(1)
}
</pre>
*See Also* "Using Mockito":https://code.google.com/p/specs/wiki/UsingMockito
h2(#spies). Spies
Spies can also be used in order to do some "partial mocking" of real objects:
<pre>
val list = new LinkedList[String]
val spiedList = spy(list)
// methods can be stubbed on a spy
spiedList.size returns 100
// other methods can also be used
spiedList.add("one")
spiedList.add("two")
// and verification can happen on a spy
there was one(spiedList).add("one")
</pre>
However, working with spies can be tricky:
<pre>
// if the list is empty, this will throws an IndexOutOfBoundsException
spiedList.get(0) returns "one"
</pre>
@doReturn@ must be used in that case:
<pre>
doReturn("one").when(spiedList).get(0)
</pre>
h2(#sbt). Run individual specs in sbt
<pre>
> test-only com.twitter.yourservice.UserSpec
</pre>
Will run just that spec.
<pre>
> ~ test-only com.twitter.yourservice.UserSpec
</pre>
Will run that test in a loop, with each file modification triggering a test run.