Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Test with specs


May 14, 2021 Scala


Table of contents


Test with specs

Extended specifications

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.

Copy

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
      }
    }
  }
}

Execute the model

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
    }
  }
}

Setup, Teardown

doBefore & doAfter

"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

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() }
}

Matchers

You have data and want to make sure it's right. Let's see how the most commonly used matchers can help you.

mustEqual

We've seen a few examples of mustEqual.

1 mustEqual 1

"a" mustEqual "a"

The references are equal, and the values are equal.

The element in the sequence

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))

The element in the map

map must haveKey(k)
map must notHaveKey(k)

map must haveValue(v)
map must notHaveValue(v)

Digital

a must beGreaterThan(b)
a must beGreaterThanOrEqualTo(b)

a must beLessThan(b)
a must beLessThanOrEqualTo(b)

a must beCloseTo(b, delta)

Options

a must beNone

a must beSome[Type]

a must beSomething

a must beSome(value)

throwA

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")
}

Write your own matcher

import org.specs.matcher.Matcher

As an invarivity

"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.

As a sample class

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.

Mocks

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

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)

Run a single specs in sbt

> 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.