May 14, 2021 Scala
Let's get started.
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
}
}
}
Arithmetic (Arithmetic) is a system constrained by specifications
add is a context.
Add two numbers (two numbers add up), and add three numbers (three numbers add up) are examples.
mustEqual
expectations
1 mustEqual 1
a common expected placeholder used before writing actual tests.
All test cases should have at least one expectation.
Notice how both tests add adds to their names? We can get rid of this duplication by nesting expectations.
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
}
}
}
}
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
}
}
}
"my system" should {
doBefore { resetTheSystem() /** user-defined reset function */ }
"mess up the system" in {...}
"and again" in {...}
doAfter { cleanThingsUp() }
}
Note
doBefore/doAfter
only run on leaf use cases.
DoFirst/doLast is used to make one-time settings. (Need an example, I don't use this)
"Foo" should {
doFirst { openTheCurtains() }
"test stateless methods" in {...}
"test other stateless methods" in {...}
doLast { closeTheCurtains() }
}
You have data and want to make sure it's right. Let's see how the most commonly used matchers can help you.
We've seen a few examples of mustEqual.
1 mustEqual 1
"a" mustEqual "a"
The references are equal, and the values are equal.
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))
map must haveKey(k)
map must notHaveKey(k)
map must haveValue(v)
map must notHaveValue(v)
a must beGreaterThan(b)
a must beGreaterThanOrEqualTo(b)
a must beLessThan(b)
a must beLessThanOrEqualTo(b)
a must beCloseTo(b, delta)
a must beNone
a must beSome[Type]
a must beSomething
a must beSome(value)
a must throwA[WhateverException]
This is a short case of a use case that has an exception thrown in the
try\catch
block.
You can also expect a specific message
a must throwA(WhateverException("message"))
You can also match exceptions:
a must throwA(new Exception) like {
case Exception(m) => m.startsWith("bad")
}
import org.specs.matcher.Matcher
"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
}
}
A contract is a metagroup that returns three values, whether the expectation is true, a message when it is true, and a message that is false.
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))
}
Using sample classes increases the re-use of your code.
import org.specs.Specification
import org.specs.mock.Mockito
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)
}
Refer to Using Mockito
Spies can do some "local mocking" on real objects:
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")
However, the use of spies can be very bizarre:
// if the list is empty, this will throws an IndexOutOfBoundsException
spiedList.get(0) returns "one"
DoReturn must be used here:
doReturn("one").when(spiedList).get(0)
> test-only com.twitter.yourservice.UserSpec
Only that specification will be run.
> ~ test-only com.twitter.yourservice.UserSpec
The test will be run in a loop, and each modification to the file will trigger a test run.