Chapter 4. Specs2

Specs2 is a testing framework with a different focus and perspective that’s different from ScalaTest’s. Much like ScalaTest’s, Specs2 works with SBT, but it has some unique differences that a developer may choose based on the functionality needed. Specs2 has a different set of matchers, a different way of structuring tests, as well as DataTable specifications, AutoExamples, and Fitnesse style specifications used for collaboration purposes with the stakeholders of your project. Specs2 tests are also unique in that they are concurrently executed in each thread.

Setting Up Specs2 in SBT

Since the book is focused on testing frameworks used with SBT, the following setup in build.sbt will bring in some dependencies and resolvers that are required for Specs2 to run.

name := "Testing Scala"

version := "1.0"

scalaVersion := "2.9.2"

resolvers ++= Seq(
  "snapshots" at "http://scala-tools.org/repo-snapshots",
  "releases"  at "http://scala-tools.org/repo-releases")

resolvers ++= Seq(
  "sonatype-snapshots" at "http://oss.sonatype.org/content
      /repositories/snapshots",
  "sonatype-releases"  at "http://oss.sonatype.org/content
      /repositories/releases")

libraryDependencies ++= Seq(
  "org.scalatest" % "scalatest_2.9.2" % "1.8" % "test" withSources()
      withJavadoc(),
  "joda-time" % "joda-time" % "1.6.2" withSources() withJavadoc(),
  "junit" % "junit" % "4.10" withSources() withJavadoc(),
  "org.testng" % "testng" % "6.1.1" % "test" withSources() withJavadoc(),
  "org.specs2" %% "specs2" % "1.12.3" withSources() withJavadoc(),

The build.sbt file must be edited to include a new element: a resolvers setting. resolvers is a list or Seq of Maven repositories for SBT to look for any required dependencies. The ++= operator is an SBT-0 overloaded operator that tells SBT to add the resolvers that follow the list that comes with SBT out of the box. The Specs2 library is housed in the oss sonatype repository, therefore the repository URLs must be declared so that sbt will know where to look. For Specs2, the oss.sonatype.org snapshot and release repository are required for the build so those URLs are added.

The first few elements in the libraryDependencies setting have already been described and used in the previous chapters. The last dependency is needed to use Specs2. The specs2 dependency is the core library and as mentioned in the first chapter, withSources() and withJavadoc() will also download jar files containing the source code and the java/scaladoc respectively in the ivy local repository.

After making these amendments to build.sbt, run sbt and enter reload in the interactive shell, or run sbt reload at the shell command prompt.

Unit Specification

The first flavor of test in Specs2 is the unit specification. It has a similar intent to ScalaTest, but is distinguished by its structure and design.

package com.oreilly.testingscala

import org.specs2.mutable._
import org.joda.time.Period

class JukeboxUnitSpec extends Specification {
  "A Jukebox" should {
    """have a play method that returns a copy of the jukebox that selects
       the first song on the first album as the current track""" in {
      val groupTherapy = new Album("Group Therapy", 2011,
        Some(List(new Track("Filmic", "3:49"),
          new Track("Alchemy", "5:17"),
          new Track("Sun & Moon", "5:25"),
          new Track("You Got to Go", "5:34"))), new Band("Above and Beyond"))
      val jukebox = new JukeBox(Some(List(groupTherapy)))
      val jukeboxNext = jukebox.play
      jukeboxNext.currentTrack.get.name must be_==("Filmic")
      jukeboxNext.currentTrack.get.period must be_==(new Period(0, 3, 49, 0))
          //Must be 3 minutes and 49 seconds
    }
  }
}

The unit specification in Spec2 starts with a string that describes the class undergoing the test. The description ends with a should method, and starts a block that should be familiar ground. Within the should block are one or more String test descriptions. The should block then ends with an in block containing the actual test code. In the previous example, “A Jukebox” specifies what is to be tested, and within the should block is one test, which describes a play method and the behavior that is expected from the test.

Note that this unit specification imports import org.specs2.mutable._. This is a different package from the acceptance specification covered later in this chapter.

The code within the in block contains a declaration of an album by the group Above and Beyond. The code also instantiates a jukebox with one album, runs the method play (which has not been implemented yet since we are TDDers). A jukebox is immutable, so invoking the method can’t change the current state of the jukebox. Instead, it instantiates a new object and returns it with a different state. That new jukebox is assigned to the variable jukeboxNext. The last two lines of the test are the expectations. The test asserts that the current track name after play has been invoked is "Filmic" and that the Period of the track is 3 minutes and 49 seconds.

When comparing and contrasting Specs2 with ScalaTest, it should strike you that the matchers are different. In JukeboxUnitSpec, the test for equality uses must be_== instead of ScalaTest’s must be (...). Each testing framework has its own set of matchers and its own strengths. In Specs2, each block of testing expectations returns a Result object.

Another interesting point is that all Specs2 tests are asynchronous and each runs in its thread using a Promise. Promises are processes that run on separate threads asynchronously using Actors and send objects, in this case an ExecutedResult to one another. Every Specs2 test sends each test as a message to an actor, and the result of the test is sent back as a ExecutedResult message.

In the previous test, the two expectations will generate a Result type of success. If any of the test expectations were to fail, a FailureException would be thrown, which encapsulates a Failure object. Contrast this with ScalaTest, which throws a TestFailedException if a test has failed. Specs2 offers a few more states to return for a test, including anError to indicate that some unknown exception has occured, skipped if the tester wishes for the test to be skipped at this time, and pending if the test is still under construction. The decision whether a test is skipped or pending follows the same logic as it does in ScalaTest.

The resulting production code is driven by the test.

package com.oreilly.testingscala

class JukeBox private (val albums:Option[List[Album]],
    val currentTrack:Option[Track]) {
  def this(albums:Option[List[Album]]) = this(albums, None)
  def readyToPlay = albums.isDefined
  def play = new JukeBox(albums, Some(albums.get(0).tracks.get(0)))
}

Specs2 offers two major constructs for authoring tests: at this time we are only covering unit specification. Specs2 offers varying ways to organize your tests. But first, a quick introduction of Specs2 matchers is in order to get a better feel for the framework.

Matchers

Specs2 offers an abundance of matchers, sometimes offering aliases for the same matchers just to offer the test developer a choice.

Simple Matchers

The following example tests for equality using Specs2 Matchers, showing how it differs from ScalaTest. The example uses Fleetwood Mac’s Rumours album and merely tests the title. The second half of the example asserts that the title of the Rumours album has nothing to do with Aerosmith’s Sweet Emotion.

val rumours= new Album("Rumours", 1977,
Some(List(new Track("Second Hand News", "2:43"),
    new Track("Dreams", "4:14"),
    new Track("Never Going Back Again", "2:02"),
    new Track("Don't Stop", "3:11"))), new Band("Fleetwood Mac"))

rumours.title must be_==("Rumours")
rumours.title must beEqualTo("Rumours")
rumours.title must_== ("Rumours")
rumours.title mustEqual "Rumours"
rumours.title should_== "Rumours"
rumours.title === "Rumours"
rumours.title must be equalTo ("Rumours")

rumours.title must not be equalTo("Sweet Emotion")
rumours.title must_!= "Sweet Emotion"
rumours.title mustNotEqual "Sweet Emotion"
rumours.title must be_!=("Sweet Emotion")
rumours.title !== "Sweet Emotion"

String Matchers

Specs2 also offers an extensive list of matchers meant specifically for strings, including some powerful regular expresssion matchers.

val boston = new Album("Boston", 1976,
  Some(List(new Track("More Than a Feeling", "4:44"),
    new Track("Peace of Mind", "5:02"),
    new Track("Foreplay/Long Time", "7:47"),
    new Track("Rock and Roll Band", "2:59"))), new Band("Boston"))

boston.title must beEqualTo("BoSTon").ignoreCase
boston.title must beEqualTo(" Boston").ignoreSpace
boston.title must beEqualTo(" BoStOn  ").ignoreSpace.ignoreCase
boston.title must contain ("os")
boston.title must startWith ("Bos")
boston.title must endWith ("ton")
boston.title must not startWith ("Char")
boston.title must have size(6)
boston.title must beMatching ("B\\w{4}n")
boston.title must beMatching ("""B\w{4}n""")
boston.title must =~("""B\w{4}n""")
boston.title must find("""(os.)""").withGroups("ost")

Most lines are self-explanatory. String can be matched with a must beMatching(...) method. The examples given use both the regular strings and raw strings, so there is no need to escape the backslash. beMatching can be replaced with =~. Finally, Specs2 string matching can find a substring with in a string and assert that the regular expression groups found are equal to the expected results. The regular expression B\w{4}n refers to a B followed by any four characters found in a word, and finishing with n.

Relational Operator Matchers

The following example reproduces the ScalaTest answer-of-life example from RelationalOperatorMatchers in ScalaTest to illustrate its relational operators. These operators can use either a DSL-like syntax or symbolic operators to set expectations.

val answerToLife = 42
answerToLife should be_<(50)
answerToLife should not be_>(50)
answerToLife must beLessThan(50)
answerToLife should be_>(3)
answerToLife must beGreaterThan(3)
answerToLife should be_<=(100)
answerToLife must beLessThanOrEqualTo(100)
answerToLife should be_>=(0)
answerToLife must beGreaterThanOrEqualTo(0)
answerToLife === (42)

Floating-Point Matchers

Specs2 also offers inexact measurements of floating-point calculations much like ScalaTest, but with a different DSL structure.

(4.0 + 1.2) must be_==(5.2)
(0.9 - 0.8) must beCloseTo (0.1, .01)
(0.4 + 0.1) must not beCloseTo (40.00, .30)
(0.4 + 0.1) must not be closeTo (40.00, .30)

Reference Matchers

Garth Brooks time again—this time analyzing reference matchers in Specs2.

val garthBrooks = new Artist("Garth", "Brooks")
val chrisGaines = garthBrooks

garthBrooks must beTheSameAs(chrisGaines)

val debbieHarry = new Artist("Debbie", "Harry")
garthBrooks must not beTheSameAs (debbieHarry)

Iterable Matchers

These use the same iterator tests from ScalaTest, with a few interesting new versions.

(Nil must be).empty
List(1, 2, 3) must not be empty
List(1, 2, 3) must contain(3)
List(1, 2, 3) must not contain (5)
List(4, 5, 6) must not contain(7, 8, 9)
List(1, 2, 3, 4, 5, 6) must contain(3, 4, 5).inOrder
List(4, 5, 6) must contain(4, 5, 6).only.inOrder
List(1, 2) must have size (2)
List(1, 2) must have length (2)

Seq and Traversable Matchers

Specs2 contains a some really neat tricks for asserting conditions within any Seq or Traversable.

List("Hello", "World") must containMatch("ll") // matches with .*ll.*
List("Hello", "World") must containMatch("Hello") // matches with .*ll.*
List("Hello", "World") must containPattern(".*llo") // matches with .*llo
List("Hello", "World") must containPattern("\\w{5}")
List("Hello", "World") must containMatch("ll").onlyOnce
List("Hello", "World") must have(_.size >= 5)
List("Hello", "World") must haveTheSameElementsAs(List("World", "Hello"))

The first and second lines determine whether any of the elements contain the string. The third and fourth lines determine whether any of the line items contain a particular pattern (regular expression). The fifth line calls a modifier method onlyOne, which asserts that ll is in a string of lists somewhere and that it occurs in that particular list only one time. The sixth matcher accepts a Boolean function and asserts that every element in the Traversable abides by it. In this case, each element must have a size greater than 5. The last line item matches the Seq on the left side with the Seq on the right side.

Map Matchers

Using the map of singers to bands shown in MapMatchers of ScalaTest, here are the analogous matchers for Specs2.

val map = Map("Jimmy Page" -> "Led Zeppelin", "Sting" -> "The Police",
    "Aimee Mann" -> "Til\' Tuesday")
map must haveKey("Sting")
map must haveValue("Led Zeppelin")
map must not haveKey ("Brian May")
map must havePair("Aimee Mann" -> "Til\' Tuesday")

All these methods are fairly straightforward. All matchers can determine whether the map has a particular key, a particular value, or pair. And each matcher can check the opposite expectations with a not modifier.

XML Matchers

Specs2 has some special sugar to determine whether two XML Elem elements are equal without regard to white space. For those still unfamiliar with Scala, Scala has built-in support for XML. Each XML element, is of type Elem; therefore, Specs2 can compare these objects and their spacing either strictly or leniently. Consider the sample ColdPlay album list that follows, where the <albums> parent tag nest five separate albums.

val coldPlayAlbums = <albums>
          <album name="Parachutes"/>
          <album name="A Rush of Blood to the Head"/>
          <album name="X&amp;Y"/>
          <album name="Viva la Vida or Death and All His Friends"/>
          <album name="Mylo Xyloto"/>
      </albums>

We might naively try to match it as follows, but the match will fail.

coldPlayAlbums must beEqualTo(<albums>
                <album name="Parachutes"/>
                       <album name="A Rush of Blood to the Head"/>
                <album name="X&amp;Y"/>
                <album name="Viva la Vida or Death and All His Friends"/>
                <album name="Mylo Xyloto"/>
        </albums>)

The stack trace of the failed test is too hideous to paste in the book, but it shows that the match does not work even though both XML elements are equal, because beEqualTo is tripped up by the different spacing. To test for XML equality, replace beEqualTo with beEqualToIgnoringSpace, or change be_== to be_==\.

coldPlayAlbums must beEqualToIgnoringSpace(<albums>
          <album name="Parachutes"/>
          <album name="A Rush of Blood to the Head"/>
        <album name="X&amp;Y"/>
        <album name="Viva la Vida or Death and All His Friends"/>
          <album name="Mylo Xyloto"/>
      </albums>)

Partial Function Matchers

Partial functions determine whether a predicate applies to their input and, if so, run the code you specify. The following example uses PartialFunctions to determine whether a given record is a gold album, a platinum album, or, as a joke, an alternative album.

val goldPartialFunction: PartialFunction[Int, String] = new PartialFunction[Int, String] {
  //States that this partial function will take on the task
  def isDefinedAt(x: Int) = x >= 500000

  //What we do if this does partial function matches
  def apply(v1: Int) = "Gold"
}

val platinumPartialFunction: PartialFunction[Int, String] = {case x: Int if
    (x >= 1000000) => "Platinum"}
val junkPartialFunction: PartialFunction[Int, String] = {case x: Int if
    (x < 500000) => "Alternative"}

val riaaCertification = goldPartialFunction
    orElse platinumPartialFunction orElse junkPartialFunction
riaaCertification must beDefinedAt (100)
riaaCertification must beDefinedBy (100 -> "Alternative")

GoldPartialFunction determines whether the number given is greater than 500,000 and, if so, returns Gold. platinumPartialFunction and junkPartialFunction are also partial functions, but are created through case statements. The variable riaaCertification combines the three partial functions into one. riaaCertification accepts an Int input to represent the number of albums sold and outputs the resulting record status.

The line riaaCertification must beDefinedAt (100) asserts that the given value is supported in the riaaCertification partial function chain. The last line asserts that the given value to a partial function will indeed return the ideal result. This example asserts that, given album sales of 100, the result will be labeled as Alternative.

Other Matchers

A few more matchers come with the Specs2 matchers, and it’s amazing that both Specs2 and ScalaTest push the envelope of matchers.

On a side note, Specs2 is very flexible when it comes to matchers, and you can make custom matchers if desired. In the following snippet of code, two matchers are created and can be used with in Specs2. beEven can be in an expectation that states 4 must beEven. "Flash" must beCapitalizedAs ("FLASH") The ^^ in the following code represents a function that returns what the expected value should be if an exception is returned. What is interesting about the last matcher is that it is built upon another Matcher, capitalized.

def beEven: Matcher[Int] = (i: Int) => (i % 2 == 0, i+" is even", i+" is odd")

def beCapitalizedAs(capitalized: String) = be_==(capitalized) ^^
    ((_:String).toUpperCase)

Acceptance Specification

An acceptance specification separates what the test is expected to do from what actually happens during the test. An oversimplified example of using a Specs2 acceptance specification follows.

package com.oreilly.testingscala

import org.specs2.Specification

class SimpleAcceptanceSpec extends Specification { def is =
  "This is a simple specification"      ^
      "and this should run f1"          ! f1 ^
      "and this example should run f2"  ! f2

    def f1 = success
    def f2 = pending
}

A very important item to note is the Specification that is imported into the package. This is org.specs2.Specification and not import org.specs2.mutable._, which is used in the unit specification that is covered in the first section of this chapter.

In SimpleAcceptanceSpec, the method that bootstraps the entire test for the class is the method is. The method returns a Fragments object containing all the examples. The SimpleAcceptanceSpec contains two examples. One will run the f1 method, as dictated after the intro string, and the next should run f2. The ! notation is used to divide the test and does so in its own separate thread.

Carets divide the specifications. Any string that does not call a method using the ! operator is considered a header for the following tests. For SimpleAcceptanceSpec, This is a simple specification is a string followed by a ^ but not a !, so it will not be considered a test and will merely echo the results of sbt or the Specs2 runner. On the other lines, the carets divide the specifications. The last line requires no final caret since it needs no division from a following specification.

The result from each of the actors are returned and the results are reported in SBT.

[info] Compiling 1 Scala source to /home/danno/testing_scala_book.git
    /testingscala/target/scala-2.9.2/test-classes...
[info] This is a simple specification
[info] + and this should run f1
[info] * and this example should run f2 PENDING
[info]
[info] Total for specification SimpleAcceptanceSpec
[info] Finished in 87 ms
[info] 2 examples, 0 failure, 0 error, 1 pending (+1)
[info] Passed: : Total 2, Failed 0, Errors 0, Passed 1, Skipped 1
[success] Total time: 5 s, completed Dec 28, 2011 9:22:26 PM

If you do not wish to run each method in its own thread, it can be annotated with an argument to make it sequential. To do this, merely add args(sequential=true) to the test as follows:

/src/test/scala/com/oreilly/testingscala/SimpleSequentialAcceptanceSpec.scala

class SimpleSequentialAcceptanceSpec extends Specification { def is =
  args(sequential = true)               ^
  "This is a simple specification"      ^
      "and this should run f1"          ! f1 ^
      "and this example should run f2"  ! f2

    def f1 = success
    def f2 = pending
}

In the specification results above, the + indicates that and this should run f1 ran successfully. The last test result shown next to PENDING and bears a * symbol to state that the test is pending. Notice that This is a simple specification has no preceding symbol, because the line never invoked an actor with !. It is just considered informational, much like the way informers are used in ScalaTest.

The previous example was boring, so it’s time to get back to the music. We’ll create a simple Artist test that adds a middle name to an artist and expects a fullName method to get the full name of the artist. The overall goal is to make sure that an Artist object can optionally include a middle name.

package com.oreilly.testingscala

import org.specs2.Specification

class ArtistAcceptanceSpec extends Specification { def is =
  "An artist should have a middle name at construction"                    ^
      """An artist should be able to be constructed with a middle name and
         get it back calling 'middleName'""" ! makeAnArtistWithMiddleName  ^
      """An artist should be able to have a full name made of the first
          and last name
         given a first and last name at construction time""" !
             testFullNameWithFirstAndLast                                  ^
      """An artist should be able to have a full name made of the first,
          middle and last name
           given a first, middle, and last name at construction time""" !
               testFullNameWithFirstMiddleAndLast


    def makeAnArtistWithMiddleName = pending
    def testFullNameWithFirstAndLast = pending
    def testFullNameWithFirstMiddleAndLast = pending
}

This is a beefier example of the test created initially in this section. Three test specifications support this topic. Each calls one of the three methods implemented in the following code and tests the results. All three testing results at the moment will return a Result of pending, because we’re still thinking over how to implement the production code.

[info] Compiling 1 Scala source to /home/danno/testing_scala_book.git/testingscala
    /target/scala-2.9.2/test-classes...
[info] An artist should have a middle name at construction
[info] * An artist should be able to be constructed with a middle name and
[info]            get it back calling 'middleName' PENDING
[info] * An artist should be able to have a full name made of the first and
    last name
[info]            given a first and last name at construction time PENDING
[info] * An artist should be able to have a full name made of the first, middle and last name
[info]              given a first, middle, and last name at construction time PENDING
[info]
[info] Total for specification ArtistAcceptanceSpec
[info] Finished in 124 ms
[info] 3 examples, 0 failure, 0 error, 3 pendings
[info] Passed: : Total 3, Failed 0, Errors 0, Passed 0, Skipped 3
[success] Total time: 6 s, completed Dec 28, 2011 10:04:01 PM

Next it’s time to fill in the pending specifications, and give them some concrete tests.

package com.oreilly.testingscala

import org.specs2.Specification

class ArtistAcceptanceSpec extends Specification { def is =
  "An artist should have a middle name at construction"                   ^
      """An artist should be able to be constructed with a Option[String]
          middle name and
         get it back calling 'middleName'""" ! makeAnArtistWithMiddleName ^
      """An artist should be able to have a full name made of the first
          and last name
         given a first and last name at construction time"""
             ! testFullNameWithFirstAndLast                               ^
      """An artist should be able to have a full name made of the first,
          middle and last name
           given a first, middle, and last name at construction time"""
               ! testFullNameWithFirstMiddleAndLast


    def makeAnArtistWithMiddleName = {
      val vaughn = new Artist("Stevie", "Ray", "Vaughn")
      vaughn.middleName must be_==(Some("Ray"))
    }

    def testFullNameWithFirstAndLast = {
      val luther = new Artist("Luther", "Vandross")
      luther.fullName must be_==("Luther Vandross")
    }

    def testFullNameWithFirstMiddleAndLast = {
      val bonJovi = new Artist("Jon", "Bon", "Jovi")
      bonJovi.fullName must be_==("Jon Bon Jovi")
    }
}

The example fills in some expectations regarding the middle names for an Artist using the artists Stevie Ray Vaughn, guitarist extraordinare; Luther Vandross, voice extraordinaire; and Jon Bon Jovi, steel horse rider extraordinaire.

Adding compile-time errors and run-time exceptions to meet the specification requirements, including breaking some of the previous tests, makes the production code more robust with some extra functionality.

package com.oreilly.testingscala

case class Artist(firstName: String, middleName: Option[String],
    lastName: String, albums: List[Album]) extends Act {
  def this(firstName: String, lastName: String) = this (firstName, None,
      lastName, Nil)

  def this(firstName: String, middleName: String, lastName: String) =
      this (firstName, Some(middleName), lastName, Nil)

  def getAlbums = albums

  def addAlbum(album: Album) = new Artist(firstName, middleName, lastName,
      album :: albums)

  def fullName = middleName match {
    case Some(x) => firstName + " " + x + " " + lastName
    case _ => firstName + " " + lastName
  }
}

The changes in Artist include an extra parameter in the default constructor, and an additional constructor to support some of the older tests that still need to create an artist with first and last name only. The last method is a fullName method that uses pattern matching to determine whether the artist has a middle name; if so, it returns the first, middle, and last names divided by spaces; if not, it returns the first and last names. The results in SBT or the Specs2 runner show the progress of TDD.

[info] Compiling 1 Scala source to /home/danno/testing_scala_book.git/testingscala/target/scala-2.9.2/test-classes...
[info] An artist should have a middle name at construction
[info] + An artist should be able to be constructed with a Option[String] middle name and
[info]            get it back calling 'middleName'
[info] + An artist should be able to have a full name made of the first and last name
[info]            given a first and last name at construction time
[info] + An artist should be able to have a full name made of the first, middle and last name
[info]              given a first, middle, and last name at construction time
[info]
[info] Total for specification ArtistAcceptanceSpec
[info] Finished in 236 ms
[info] 3 examples, 0 failure, 0 error
[info] Passed: : Total 3, Failed 0, Errors 0, Passed 3, Skipped 0
[success] Total time: 5 s, completed Dec 28, 2011 10:45:29 PM

Specs2 offers formatting tags to prettify the end results of the tests. Some formatting is implicit. Any text that directly follows another is indented under the preceding text. Thus, in the previous example, An artist should have a middle name at construction is followed by a ^ to delineate the end of the line. Since the next element following the ^ is also a String, it is indented and labeled with a + mark.

Adjacent specification examples (examples defined by both the string description and the call to the test) will have the same indentation level. Thus, in the ArtistAcceptanceSpec, the two specification examples will have the same indentation level.

"""An artist should be able to be constructed with a Option[String] middle name and
   get it back calling 'middleName'""" ! makeAnArtistWithMiddleName                   ^
"""An artist should be able to have a full name made of the first and last name
   given a first and last name at construction time""" ! testFullNameWithFirstAndLast

If either the next string after the specification or the specification example is not to be indented, you can add a ^p tag after the previous caret. The ^p tag terminates the line with a carriage return and decrements the indentation by 1 for the next specification example or string. This is nearly analogous to the <p> tag in HTML/XHTML. In the next example, a ^p is added to separate the test, since the next test will focus on creating an alias, and it is a perfect place to add a paragraph delimiter.

package com.oreilly.testingscala

import org.specs2.Specification

class ArtistAcceptanceSpec extends Specification { def is =
    "An artist should have a middle name at construction"                                                    ^
      """An artist should be able to be constructed with a Option[String] middle name and
         get it back calling 'middleName'""" ! makeAnArtistWithMiddleName                                    ^
      """An artist should be able to have a full name made of the first and last name
         given a first and last name at construction time""" ! testFullNameWithFirstAndLast                  ^
      """An artist should be able to have a full name made of the first, middle and last name
           given a first, middle, and last name at construction time""" ! testFullNameWithFirstMiddleAndLast ^
                                                                                                             p^
    "An artist should have an alias"                                                                         ^
      """method called withAlias(String) that returns a copy of Artist with an alias"""  ! testAlias

    //Code removed for brevity

    def testAlias = {pending}
}

Here, ^p is used to visually separate one “paragraph” from another, displaying the testing categories with a clear break. Separating testing categories using ^p is not optimal, as we’ll see later, but for now fits the purpose. The end result in the output will also show the separation.

48. Waiting for source changes... (press enter to interrupt)
[info] Compiling 1 Scala source to /home/danno/testing_scala_book.git/testingscala/target/scala-2.9.2/test-classes...
[info] An artist should have a middle name at construction
[info] + An artist should be able to be constructed with a Option[String] middle name and
[info]            get it back calling 'middleName'
[info] + An artist should be able to have a full name made of the first and last name
[info]            given a first and last name at construction time
[info] + An artist should be able to have a full name made of the first, middle and last name
[info]              given a first, middle, and last name at construction time
[info]
[info] An artist should have an alias
[info] * method called withAlias(String) that returns a copy of Artist with an alias PENDING
[info]
[info] Total for specification ArtistAcceptanceSpec
[info] Finished in 266 ms
[info] 4 examples, 0 failure, 0 error, 1 pending (+1)
[info] Passed: : Total 4, Failed 0, Errors 0, Passed 3, Skipped 1
[success] Total time: 5 s, completed Dec 30, 2011 3:36:28 PM

Again, remember that ^p decrements the next indentation by 1. If the line is indented 5 levels and is followed by ^p, the next line will be at indentation level 4. To go back to 0, use the end^ tag instead.

class ArtistAcceptanceSpec extends Specification { def is =
    "An artist should have a middle name at construction"                                                    ^
      """An artist should be able to be constructed with a Option[String] middle name and
         get it back calling 'middleName'""" ! makeAnArtistWithMiddleName                                    ^
      """An artist should be able to have a full name made of the first and last name
         given a first and last name at construction time""" ! testFullNameWithFirstAndLast                  ^
      """An artist should be able to have a full name made of the first, middle and last name
           given a first, middle, and last name at construction time""" ! testFullNameWithFirstMiddleAndLast ^
                                                                                                             end^
    "An artist should have an alias"                                                                         ^
      """method called withAlias(String) that returns a copy of Artist with an alias"""  ! testAlias


    def makeAnArtistWithMiddleName = {...}

    def testFullNameWithFirstAndLast =  {...}

    def testFullNameWithFirstMiddleAndLast = {...}

    def testAlias = {pending}

Although end^ will end the paragraph, it will not add another line. You can get both by using both an end^ and a ^p, but a combination endp^ marker also creates the desired effect.

You can get even more control over indention through the bt^ or t^ tags. For the sake of example, if the first part of the ArtistAcceptanceSpec was written with a three ^t tags after the end of the initial string, the end result would indent the next line three times.

 "An artist should have a middle name at construction"                                        ^ t ^ t ^ t ^
      """An artist should be able to be constructed with a Option[String] middle name and
         get it back calling 'middleName'""" ! makeAnArtistWithMiddleName                                 ^

 //code omitted for brevity

The t method that does the work of indenting can also accept an Int (a Scala Int) that indents the next line by the number indicated. Rewriting the short example above with the Int parameter produces:

 "An artist should have a middle name at construction"                                             ^ t(3) ^
      """An artist should be able to be constructed with a Option[String] middle name and
         get it back calling 'middleName'""" ! makeAnArtistWithMiddleName                                 ^

 //code omitted for brevity

In contrast, ^bt^ backtabs. To manipulate tabs for the subsequent line, the same rules apply, only in reverse.

We’ll add what we just covered and create an implementation for aliasTest in ArtistAcceptanceSpec.

package com.oreilly.testingscala

import org.specs2.Specification

class ArtistAcceptanceSpec extends Specification { def is =
    "An artist should have a middle name at construction"                                             ^ t(3) ^
      """An artist should be able to be constructed with a Option[String] middle name and
         get it back calling 'middleName'""" ! makeAnArtistWithMiddleName                                    ^
                                                                                                             p^
      """An artist should be able to have a full name made of the first and last name
         given a first and last name at construction time""" ! testFullNameWithFirstAndLast                  ^
      """An artist should be able to have a full name made of the first, middle and last name
           given a first, middle, and last name at construction time""" ! testFullNameWithFirstMiddleAndLast ^
                                                                                                             endp^
    "An artist should have an alias"                                                                         ^
      """method called withAlias(String) that returns a copy of Artist with an alias""" ! testAlias


    def makeAnArtistWithMiddleName = {
      val vaughn = new Artist("Stevie", "Ray", "Vaughn")
      vaughn.middleName must be_==(Some("Ray"))
    }

    def testFullNameWithFirstAndLast = {
      val luther = new Artist("Luther", "Vandross")
      luther.fullName must be_==("Luther Vandross")
    }

    def testFullNameWithFirstMiddleAndLast = {
      val bonJovi = new Artist("Jon", "Bon", "Jovi")
      bonJovi.fullName must be_==("Jon Bon Jovi")
    }

    def testAlias = {
        val theEdge = new Artist("David", "Howell", "Evans").withAlias("The Edge")
        theEdge.alias must be_==(Some("The Edge"))
    }
}

The result of the test-driven development will in turn cause changes in the Artist production code—notably, the new withAlias method and a change in the main constructor, as well as the call to that constructor from the auxiliary constructors.

package com.oreilly.testingscala

case class Artist(firstName: String, middleName: Option[String], lastName: String, albums: List[Album], alias:Option[String]) extends Act {
  def this(firstName: String, lastName: String) = this (firstName, None, lastName, Nil, None)

  def this(firstName: String, middleName: String, lastName: String) = this (firstName, Some(middleName), lastName, Nil, None)

  def getAlbums = albums

  def addAlbum(album: Album) = new Artist(firstName, middleName, lastName, album :: albums, alias)

  def fullName = middleName match {
    case Some(x) => firstName + " " + x + " " + lastName
    case _ => firstName + " " + lastName
  }

  def withAlias(alias:String) = new Artist(firstName, middleName, lastName, albums, Some(alias))

Chaining Tests

Each test in specs can be chained to hand off calls and functions to another. This should be no surprise, since the acceptance specification is merely a collection of objects of the Result type, or Strings and methods that return a Result type. There is nothing particularly magical about acceptance specifications.

Given/When/Then

In ScalaTest, GivenWhenThen structures were in the form of an Informer, an object whose only job is to output any extra information to the end report of the test suite. In Specs2, GivenWhenThen takes on a totally different role. Within the AcceptanceSpec, GivenWhenThen is a Result fragment object that holds states—one possible state being Given, another When, and the final one Then—and it passes that state on through to the test. GWTs are built by inserting GWT “steps” between the textual descriptions and that those steps keep the state of the current execution and are eventually translated to regular Text, Step, and Example fragments. Each object is in charge of taking in some data—either from the previous state or from a String specification—and creating another object, then passing that object on like a baton in a relay race.

GivenWhenThen in Specs2, as in ScalaTest, is used to mentally reinforce the definition of the test into distinct ideas. The examples in this section will be done in parts, due to a somewhat steep learning curve. The first example shows the basic parts of the test without the supporting objects it requires.

package com.oreilly.testingscala

import org.specs2.Specification

class GivenWhenThenAcceptanceSpec extends Specification { def is = {
    "Demonstrating a Given When Then block for a Specs2 Specification".title ^
    "Given the first name ${David} and the last ${Bowie} create an Artist" ^ setUpBowie ^
    "When we add the artist to an album called ${Hunky Dory} with the year ${1971}" ^ setUpHunkyDory ^
    "And when an the album is added to a jukebox" ^ addTheAlbumToAJukebox ^
    "Then the jukebox should have one album whose name is ${Hunky Dory}" ^ result

    object setUpBowie
    object setUpAlbum
    object addTheAlbumToAJukebox
    object result
}

Let’s take the example a little bit at a time. The class declaration and the is method have been covered already in the previous sections. The first string is the title of the test, and is marked as such by the title method.

The second statement is the Given statement. The words David and Bowie, which are encased in ${}, will be used in the setUpBowie object to create an Artist that will passed down the test.

The next statement, setUpHunkyDory, will take the words Hunky Dory and 1971, which are also encased in ${} and use them to create an Album that will be passed down the test. The following statement will add an album to a JukeBox. A jukebox instance will be created and passed down to the last link of the specification. This link will do the final expectations and return a proper Result.

The example ends by defining objects that will parse the contents of ${} and spit out the appropriate objects for the other specification links to take and return the results needed by the next test in sequence.

The next example extends the Given, When, and Then parent classes with appropriate type parameters. This should show how the analogy “passing the baton in a relay race” is appropriate.

//Code removed for brevity
object setUpBowie extends Given[Artist]
object setUpHunkyDory extends When[Artist, Album]
object addTheAlbumToAJukebox extends When[Album, JukeBox]
object result extends Then[JukeBox]
//Code removed for brevity

Now, the Given, When, and Then rules are in place with their type parameters. The type parameters are the key to understanding how to use GivenWhenThen constructs. First, setUpBowie is used as the Given object. The type parameter is the return parameter, and states that it should return an object of type Artist. Since the Given object returns Artist, there must be either a When object or a Then object that accepts an Artist as its first type parameter, and setUpHunkyDory will answer that call.

setUpHunkyDory is a When object that has two type parameters (as all When objects must). In this case, the first is the type created by the previous link Given object, Artist. The second is the type returned by this object, in this case an Album. In short, setUpHunkyDory will take in an Artist and return an Album.

Next in the chain is a When[Album, Jukebox] object that will take in an Album, the one being returned by setUpHunkyDory, and return an instance of a JukeBox. The final link in the chain is result, which is an object that will take the last object created in this relay race, the Jukebox created by addTheAlbumToAJukebox.

For the beginner, it might be a good idea to start out with the objects to see how they work and use what is learned to sculpt the specifications accordingly.

 object setUpBowie extends Given[Artist] {
    def extract(text: String) = {
      val tokens = extract2(text)
      new Artist(tokens._1, tokens._2)
    }
  }

In setUpHunkyDory, the object extends When with parameter types Artist and Album. This indicates that the previous step must return an Artist and that the setUpHunkyDory object must return an Album. The extract method in the object is slightly different because it takes two parameters. The first is the Artist that was returned from the Given case, and the second is the text of the specification. Here extract2 is used to parse out the values into a tuple with the values HunkyDory and 1971. Since an Album is the return type (again, because it’s listed as the second type parameter of When), the last line of the extract method will return a new Album using the information parsed and the Artist object that was passed down.

  object setUpHunkyDory extends When[Artist, Album] {
    def extract(p: Artist, text: String) = {
      val tokens = extract2(text)
      new Album(tokens._1, tokens._2.toInt, p)
    }
  }

For addTheAlbumToAJukebox the object also extends a When with the type parameters Album and Jukebox. In this implementation, extracting the text isn’t required since the specification string doesn’t contain any required data. The only requirement is the Album that was returned by the previous object, setUpHunkyDory. With that album, a new JukeBox is instantiated and the album is added and returned for the next object to use.

  object addTheAlbumToAJukebox extends When[Album, JukeBox] {
    def extract(p: Album, text: String) = new JukeBox(Some(List(p)))
  }

The final link is the result object, which extends the Then abstract class. The type parameter is the type that is required from the previous object, addTheAlbumToAJukebox, which of course is an Album type. The extract method is the same as it was from the When class. The first parameter is the object passed down, and the second text parameter is the String from its accompanying specification string. The difference between extending When and extending then is that the return type in the Then class has to be a Result type since that is the last element of the chain. In the following example, the Result returned is the expectation that the albums in the jukebox total to 1.

  object result extends Then[JukeBox] {
    def extract(t: JukeBox, text: String) = t.albums.get must have size (1)
  }

The GivenWhenThen specification takes a little work to understand, but once the test developer gets a thorough understanding of how the return types pass results down the chain, the structure becomes self-explanatory, useful, and at times reuseable.

The end result for the previous GivenWhenThen example should return the following:

[info] Given the first name David and the last Bowie create an Artist
[info] When we add the artist to an album called Hunky Dory with the year 1971
[info] And when an the album is added to a jukebox
[info] + Then the jukebox should have one album whose name is Hunky Dory
[info]
[info] Total for specification Demonstrating a Given When Then block for a Specs2 Specification
[info] Finished in 26 ms
[info] 1 example, 0 failure, 0 error
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 0
[success] Total time: 2 s, completed Jan 3, 2012 2:54:47 PM

Data Tables

Data tables are ASCII tables that contain sample values and the end result. Once the table is established, a function test case is attached to the end to verify that all the values provided match the criteria. In the following example, we’ll show a test that makes sure the date of an album’s release matches the age correctly.

package com.oreilly.testingscala

import org.specs2.Specification
import org.specs2.matcher.DataTables

class AlbumAgeDataTableSpecification extends Specification with DataTables {def is =
  "Trying out a table of values for testing purposes to determine the age of albums".title ^
  """The first column is the album name, the second is a band name,
     and third is a year, and the fourth is the age from the year 2070""" ! ageTable

  def ageTable =
    "Album Name"          | "Band Name"                 | "Year" | "Age" |
    "Under the Iron Sea"  !! "Keane"                    ! 2006   !   64  |
    "Rio"                 !! "Duran Duran"              ! 1982   !   88  |
    "Soul Revolution"     !! "Bob Marley & the Wailers" ! 1971   !   99  |> {
      (a:String, b:String, c:Int, d:Int) ⇒ new Album(a, c, new Band(b)).ageFrom(2070) must_== d
    }
}

The class definition is an acceptance specification with the inclusion of a DataTables trait. The DataTable trait contains case classes and methods that makes the data table magic happen. The example just shown has delineators,the same overall setup as we’ve seen in the past: a def declaration, a title, a string specification, and a call to some Result object or method that returns a Result. What is different, of course, is the data table.

After the def ageTable = method declaration, the first line contains header information for the test, delineated by a | pipe character. Each subsequent row of data takes different delineators, either a !! or a ! to delineate each column. The exception is the last column of each row, because the end of the row is marked with another | pipe character. A data table can go indefinitely until the a row is terminated with |>, at which point the table is going to be executed and returned with a Result.

Each column is sent into the function in the form of a Tuple. Since there are four columns in our table, it will require a Tuple4 parameter that receives each row of data. The function receives the album name as a string, the band name as a string, the year as an Int, and the expected Age as an Int. For each row of data, the function will create an Album, assign the name and year, and create a Band object from the second column. Then it calls a method called ageFrom, which does not yet exist, that takes the current year and returns the age of the album as an Int. Finally, an expectation checks whether the age returned equals the fourth column of the data table, Age.

The use of the current year in the method is intentional. It’s a good idea not to use the current year in actual production code because the year constantly changes. That means that any test is likely to fail over time. The next year will likely kill all your unit tests. Having consistent unit tests is not the only reason why the current year, or any temporal information, should not be calculated in actual production code. The other good reason is that it makes the code less reusable. If, for instance, there is a requirement to calculate future statistics on code, it seems a lot of work to redo the guts of a class or object and extract the current date just to do some forecasting. It’s nice to leave such hard dependencies out and plug in what is needed when it is needed.

The results of the test after changing production code are as follows.

[info] Compiling 1 Scala source to /home/danno/testing_scala_book.git/testingscala/target/scala-2.9.2/test-classes...
[info] + The first column is the album name, the second is a band name,
[info]      and third is a year, and the fourth is the age from the year 2070
[info]
[info] Total for specification Trying out a table of values for testing purposes to determine the age of albums
[info] Finished in 108 ms
[info] 1 example, 0 failure, 0 error
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 0
[success] Total time: 2 s, completed Jan 3, 2012 2:51:46 PM

The changes to the production code include the addition of ageFrom.

package com.oreilly.testingscala

class Album(val title: String, val year: Int, val tracks: Option[List[Track]], val acts: Act*) {

  require(acts.size > 0)

  def this(title: String, year: Int, acts: Act*) = this (title, year, None, acts: _*)

  def ageFrom(now: Int) = now - year
}

Tagging

Specs2 also has a tagging feature that allows the developer to categorize tests. Categorization of tests can be used in both in a unit specification and an acceptance specification. Tests can be filtered in SBT or in the specification itself in case you wish to create one specification that would run other specifications. The example below is an acceptance specification that uses tags to denote a category for the test. The first specification in the test is categorized with the strings "timeLength" and "threeTracks". The second test is categorized also with the String "timeLength" and with "zeroTracks". In order to make these work, you must import the trait org.specs2.specification.Tags and add that trait to the specification. In the example below, with Tags is added to the specification.

import org.specs2.Specification
import org.specs2.specification.Tags
import org.joda.time.Period

class Specs2TagAcceptanceSpecification extends Specification with Tags {
  def is =
    "The total time of an album is based on the sum of the tracks".title ^
      "When an album is given three tracks" ! testThreeTracks ^ tag("timeLength", "threeTracks") ^
      "When an album is given zero tracks" ! testZeroTracks ^ tag("timeLength", "zeroTracks")

  def testThreeTracks = {
    val beyonceFirstAlbum = new Album("Dangerously in Love", 2003,
      Some(List(
        new Track("Crazy In Love", "3:56"),
        new Track("Naughty Girl", "3:29"),
        new Track("Baby Paul", "4:05")
      )), new Artist("Beyonce", "Knowles"))
    beyonceFirstAlbum.period must be_== (new Period(0, 10, 90, 0))
  }

  def testZeroTracks = {
    val frankZappaAlbum = new Album("We're only in it for the Money", 1968, None, new Band("The Mothers of Invention"))
    frankZappaAlbum.period must be_== (Period.ZERO)
  }
}

To run the test using tags in SBT use test-only or ~test-only with the name of the test followed by -- include, with the name of the tags that you wish to run delimited with a comma. For example, to run only zeroTracks tests from the Specs2TagAcceptanceSpecification, the following command line would work:

> test-only com.oreilly.testingscala.Specs2TagAcceptanceSpecification -- include zeroTracks
[info] + When an album is given zero tracks
[info]
[info] Total for specification The total time of an album is based on the sum of the tracks
[info] Finished in 145 ms
[info] 1 example (+1), 0 failure, 0 error
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 0
[success] Total time: 1 s, completed Jan 4, 2012 2:57:31 PM

Again it is worth noting that --include can accept any number of tag keywords, and every test that contains the tags specified will run. In the next example, we show that we are including the tags zeroTracks, completeAlbums, classical, and short-tests to the list of tests that we wish to include.

> test-only com.oreilly.testingscala.Specs2TagAcceptanceSpecification -- include zeroTracks completeAlbums classical short-tests
[info] + When an album is given zero tracks
[info]
[info] Total for specification The total time of an album is based on the sum of the tracks
[info] Finished in 94 ms
[info] 1 example, 0 failure, 0 error
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 0
[success] Total time: 1 s, completed Jan 4, 2012 3:04:24 PM

Fixtures

Fixtures, the ability to call a start method and a close method for a test class mostly standard Scala programming features. Depending on what kind of test you are using, there are different ways to create fixtures. Specs2 tends to take the idealistic stance that most of what you need to do can be achieved using Scala. Let’s take a first example of using a mutable list that is shared by two tests in a Specification.

class Specs2WithoutFixtures extends Specification { def is =
  "Add an album to a shared list" ! test1 ^
  "Remove an album to a shared list" ! test2

  lazy val lst = scala.collection.mutable.Buffer(
    new Album("Fly By Night", 1974, new Band("Rush")),
    new Album("19", 2008, new Artist("Adele", "Laurie", "Adkins").withAlias("Adele")))

  def test1 = {
    lst.append(new Album("Prokofiev and Rachmaninoff: Cello Sonatas", 1991, new Artist("Yo", "Yo", "Ma")))
    lst must have size(3)
  }

  def test2 = lst.drop(1) must have size(1)
}

In the above example, test1 uses the shared list mutable lst and appends one album by Yo Yo Ma to that list. After test1 is run since according to our spec “Add an album to a shared list” starts first. “Remove an album to a shared list” starts next. Each test, as you can tell, was written with the assumption that either the lst provided would be unique to the test and not shared. If this test is run, then failure will occur because the lst is shared across test1 and test2, and test2 fails because we were assuming that the lst originally had two items.

> test-only com.oreilly.testingscala.Specs2WithoutFixtures
[info] Compiling 1 Scala source to /home/danno/testing_scala_book.svn/testingscala/target/scala-2.9.2/test-classes...
[info] + Add an album to a shared list
[error] x Remove an album to a shared list
[error]   'Album[19], Album[Prokofiev and Rachmaninoff: Cello Sonatas]' doesn't have size 1 but size 2 (Specs2CaseClassContext.scala:13)
[info]
[info] Total for specification Specs2WithoutFixtures
[info] Finished in 143 ms
[info] 2 examples, 1 failure, 0 error

This is because in this specification we have a shared mutable state. How do we go about creating a unique list for each test? Perhaps the easiest and most functional way is to make the lst an immutable data structure which is the default.

class Specs2WithoutFixturesButImmutable extends Specification { def is =
  "Add an album to a shared list" ! test1 ^
  "Remove an album to a shared list" ! test2

  lazy val lst = List(
    new Album("Fly By Night", 1974, new Band("Rush")),
    new Album("19", 2008, new Artist("Adele", "Laurie", "Adkins").withAlias("Adele")))

  def test1 = {
    val result = lst :+ new Album("Prokofiev and Rachmaninoff: Cello Sonatas", 1991, new Artist("Yo", "Yo", "Ma"))
    result must have size(3)
  }

  def test2 = lst.drop(1) must have size(1)
}

Another score for immutability. So shared state is often the least of your worries with immutability. Let’s say that although there may be teams where you do have to manage shared states across tests, or you require a method to initialize a database or a service, having a setup and teardown method (using JUnit parlance) is needed. Given the shared state example Specs2WithoutFixtures above, a set up method can be established using a Scope trait in either a unit specification or an acceptance specification. In an acceptance specification all that is required is a trait that extends Scope and extends that trait in a case class that envelops all tests that require the scoped setup.

class Specs2WithScope extends Specification { def is =
  "Add an album to a shared list" ! AddItemTest().test ^
  "Remove an album to a shared list" ! RemoveItemTest().test

  trait ListMaker  {
    lazy val lst = scala.collection.mutable.Buffer(
      new Album("Fly By Night", 1974, new Band("Rush")),
      new Album("19", 2008, new Artist("Adele", "Laurie", "Adkins").withAlias("Adele")))
  }

  case class AddItemTest() extends ListMaker   {
    def test = {
      lst.append(new Album("Prokofiev and Rachmaninoff: Cello Sonatas", 1991, new Artist("Yo", "Yo", "Ma")))
      lst must have size(3)
    }
  }

  case class RemoveItemTest() extends ListMaker {
    def test = lst.drop(1) must have size(1)
  }
}

Each case class will have one or more test methods in it. Each case class AddItemTest() and RemoveItemTest() extends from ListMaker, which is a trait. The reason a trait works is that its state is unique to each class that extends it. Therefore AddItemTest() and RemoveItemTest will each have its own list to test.

How do we achieve the same thing for a unit specification? Remember that a unit specification is a specification like its sibling AcceptanceSpecification but with a different form. Instead of calling methods from the specification, the tests are run within an in clause. Below is the same test as the acceptence specifications that we have been using, but restructured as a unit specification.

import org.specs2.mutable.Specification
import org.specs2.specification.Scope


class Specs2UnitSpecificationFixtures extends Specification {
  "Add an album to a shared list" in new ListMaker {
    lst.append(new Album("Prokofiev and Rachmaninoff: Cello Sonatas", 1991, new Artist("Yo", "Yo", "Ma")))
    lst must have size (3)
  }
  "Remove an album to a shared list" in new ListMaker {
    lst.drop(1) must have size (1)
  }

  trait ListMaker extends Scope {
    lazy val lst = scala.collection.mutable.Buffer(
      new Album("Fly By Night", 1974, new Band("Rush")),
      new Album("19", 2008, new Artist("Adele", "Laurie", "Adkins").withAlias("Adele")))
  }
}

By now all the players should be familiar, except now each of the tests are inline with the specification, and after the in clause we instantiate an anonymous trait that will make available a unique lst of albums for each test. This is what we want. But to actually make this work, each trait that must extend ‘org.specs2.specification.Scope` in order for Specs2 to understand that the trait will return Result type, which is required by the framework. Without extending the Scope trait, Specs2 will complain that it cannot implicitly convert a ListMaker into a org.specs2.execute.Result.

For teardown or cleanup methods, each specification has its own way of doing things. In the unit specification, you continue to use the trait strategy but instead of using Scope you use the org.specs2.mutable.After trait, which will give a method for you to override—-appropriately called after. The after method will be called by Specs2 when the test is completed, whether the test fails or succeeds. This next example uses the same data as the previous example but uses the After trait instead of the Scope trait.

class Specs2UnitSpecificationWithAfter extends Specification {
  "Add an album to a shared list" in new ListMaker {
    lst.append(new Album("Prokofiev and Rachmaninoff: Cello Sonatas", 1991, new Artist("Yo", "Yo", "Ma")))
    lst must have size (3)
    def after {printf("Final tally: %d\n", lst.size)}
  }

  "Remove an album to a shared list" in new ListMaker {
    lst.drop(1) must have size (1)
    def after {printf("Final tally: %d\n", lst.size)}
  }

  trait ListMaker extends After {
    lazy val lst = scala.collection.mutable.Buffer(
      new Album("Fly By Night", 1974, new Band("Rush")),
      new Album("19", 2008, new Artist("Adele", "Laurie", "Adkins").withAlias("Adele")))
  }
}

Since After requires that we implement an after method that returns Any object, we can define an after method in each anonymous instantiation of ListMaker for every test that we run. In each of tests, the after method returns Unit, which is a type that represents what void is in Java, C, and C+. This can be refactored though: since the implementation of +after is the same across multiple tests we can move that down to the ListMaker trait, where it will look cleaner and still run successfully.

class Specs2UnitSpecificationWithAfter extends Specification {
  "Add an album to a shared list" in new ListMaker {
    lst.append(new Album("Prokofiev and Rachmaninoff: Cello Sonatas", 1991, new Artist("Yo", "Yo", "Ma")))
    lst must have size (3)
  }

  "Remove an album to a shared list" in new ListMaker {
    lst.drop(1) must have size (1)
  }

  trait ListMaker extends After {
    lazy val lst = scala.collection.mutable.Buffer(
      new Album("Fly By Night", 1974, new Band("Rush")),
      new Album("19", 2008, new Artist("Adele", "Laurie", "Adkins").withAlias("Adele")))
    def after {printf("Final tally: %d\n", lst.size)}
  }
}

Below are the results of the run. The final tallies are printed after the tallies are run. The test reporting occurs after the tests have run.

> test-only com.oreilly.testingscala.Specs2UnitSpecificationWithAfter
[info] Compiling 1 Scala source to /home/danno/testing_scala_book.svn/testingscala/target/scala-2.9.2/test-classes...
Final tally: 3
Final tally: 2
[info] + Add an album to a shared list
[info] + Remove an album to a shared list
[info]
[info] Total for specification Specs2UnitSpecificationWithAfter
[info] Finished in 147 ms
[info] 2 examples, 0 failure, 0 error
[info] Passed: : Total 2, Failed 0, Errors 0, Passed 2, Skipped 0
[success] Total time: 2 s, completed Jan 5, 2012 10:49:04 AM

There are multiple solutions for creating fixtures in Specs2. Specs2 has an Around trait that can do the same as previous examples. What is different with the Around trait is that there is one place where a programmer can create logic to be wrapped around the test. You may find the trait is similar to either an JavaEE Interceptor or a servlet specification Filter. The recipe for the Around trait is to first do setup, then call the test, which is given as a function parameter, and when the function parameter returns, perform any cleanup that is required.

The object below logs the start and stop of the test. It’s a simple fixture. The example uses a simple println to output before and after messages when the test is run. Between each of the outputs, the test is run by calling t, and the result is captured in a variable, result. That reference is held until the end of the test, when it is returned.

object log extends org.specs2.specification.Around {
  def around[T <% Result](t: =>T):Result = {
    println("Start process")
    val result:T = t
    println("End process")
    result
  }
}

What can be very confusing is the [T <% Result]. This is Scala’s type bounds, if there is a converter in the scope of this object that can convert any type T into a Result. That means whatever the type T is, it either has to be a type that is of type Result or it is of type of something that can be converted into a type Result. The log extends the org.specs2.specification.Around trait which mandates that the method def around be declared. The def around method accepts a function parameter of type Unit=>T, which of course can be shortened to =>T. As a reminder, Unit is the equivalent of a void in Java.

Below we run the test using the log Around trait to do our println log of the test in an acceptance specification. In the specification, we run not just e1 but log(e1). This will wrap the log object around the test method so that when the test runs the around method in the log will run.

class UsingTheAroundProcess extends Specification {
  def is =
    "this will log something before running" ! log(e1)

  lazy val lst = List(
    new Album("Storms of Life", 1986, new Artist("Randy", "Travis")),
    new Album("The Bad Touch", 1999, new Band("Bloodhound Gang")),
    new Album("Billie Holiday Sings", 1952, new Artist("Billie", "Holiday")))

  def e1 = {
    println("Running test")
    lst.drop(1) must have size (2)
  }
}

Running the above test, we find that everything falls into place. Start Process is invoked first, Running Test is next, and finally End Process is displayed. The advantage of using an Around trait is that this is now extremely reusable. The object can be used in other test methods in other tests.

One of the disadvantages of using an Around trait is that you cannot get access to the state of objects that have been declared inside of the trait. If you establish anything like a service and database, or any object state, you cannot get access to it. If you need this kind of functionality, the Outside trait is useful for declaring a stateful object that needs to instantiated and set up before the test runs. Once the object is set, it can then be delivered to the tester in a function parameter.

Note

Please don’t use external services or databases in unit tests or test-driven development. That is reserved for integration testing or functional testing.

The next example uses the Outside trait to set up a Joda-Time DateTime object that provides the current date and time to the test. The withCurrentDate object extends the Outside trait with the parameterized type DateTime—the type that is the focus the trait. Extending the trait requires the outside method to be declared, which should return the object to be used inside the test. In our example, that is the current DateTime.

object withCurrentDate extends org.specs2.specification.Outside[DateTime] {
  def outside = new DateTime
}

For good measure we will also include a withFakeDate object that is also used inside a test, although this Outside trait will return a fixed date of January 2, 1980.

object withFakeDate extends org.specs2.specification.Outside[DateTime] {
     def outside = new DateMidnight(1980, 1, 2).toDateTime
}

Now we can use these objects inside a test, and much like the Around trait, we can use the Outside trait nearly the same way, except that it will provide information before running the test. In the next example, UsingDates is also an acceptance specification. Instead of calling the test method testDate straight away, it is wrapped with the Outside trait that provides the needed date. Each specification is given a different date but calls one test method with each of those different dates.

class UsingDates extends Specification {def is =

  "this will use the real date" ! (withCurrentDate(x => testDate(x))) ^
  "this will use a fake date" ! (withFakeDate(x => testDate(x)))

  def testDate(x: DateTime) = (x.plusDays(20).isAfterNow)
}

This test will run for the top specification but not the bottom one, since the arithmetic doesn’t add up.

[info] Compiling 1 Scala source to /home/danno/testing_scala_book.svn/testingscala/target/scala-2.9.2/test-classes...
[info] + this will use the real date
[error] x this will use a fake date
[error]   the value is false (Specs2AcceptanceSpecificationFixtures.scala:145)
[info]
[info] Total for specification UsingDates
[info] Finished in 539 ms
[info] 2 examples, 1 failure, 0 error
[error] Failed: : Total 2, Failed 1, Errors 0, Passed 1, Skipped 0
[error] Failed tests:
[error]     com.oreilly.testingscala.UsingDates
[error] {file:/home/danno/testing_scala_book.svn/testingscala/}Testing Scala/test:test-only: Tests unsuccessful
[error] Total time: 3 s, completed Jan 6, 2012 2:49:55 PM

Finally, what if you wish to have the best of both Around and Outside? Of course there is an AroundOutside that provides that specific solution. The following code is a logWithFakeDateTime object that extends the AroundOutside[DateTime] trait. The trait requires that the tester use both the outside and around methods. Based on the previous examples, we can infer that outside will set up the object that will be used inside the test, and the around method will be run around the test method using the same object.

object logWithFakeDateTime extends org.specs2.specification.AroundOutside[DateTime] {
  def outside = new DateMidnight(1980, 1, 2).toDateTime

  def around[T <% Result](t: ⇒ T) = {
    println(outside + ": Start process")
    val result = t
    println(outside + ": End process")
    result
  }
}

Now we can make use of this trait inside the test both as an Around and an Outside.

class UsingTheAroundOutsideProcess extends Specification {
  def is =
    "this will log something before running" ! logWithFakeDateTime(dateTime ⇒ e1(dateTime))

  lazy val lst = List(
    new Album("Storms of Life", 1986, new Artist("Randy", "Travis")),
    new Album("The Bad Touch", 1999, new Band("Bloodhound Gang")),
    new Album("Billie Holiday Sings", 1952, new Artist("Billie", "Holiday")))

  def e1(dt: DateTime) = {
    println("Running test at " + dt)
    lst.drop(1) must have size (2)
  }
}

In the above example, logWithFakeDateTime is given the function that accepts the DateTime object as a parameter that is created within the OutsideAround object. We use that DateTime object inside the test method since we need it for our test. Remember that this is also the Around trait, so whatever logic that we stated in the around method will be run. The end result will show the full combination.

> test-only com.oreilly.testingscala.UsingTheAroundOutsideProcess
[info] Compiling 1 Scala source to /home/danno/testing_scala_book.svn/testingscala/target/scala-2.9.2/test-classes...
2012-01-06T15:08:42.147-06:00: Start process
Running test at 2012-01-06T15:08:42.265-06:00
2012-01-06T15:08:42.280-06:00: End process
[info] + this will log something before running
[info]
[info] Total for specification UsingTheAroundOutsideProcess
[info] Finished in 188 ms
[info] 1 example, 0 failure, 0 error
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 0
[success] Total time: 3 s, completed Jan 6, 2012 3:08:42 PM

Analyzing the end result, we find that the first item, 2012-01-06T15:08:42.147-06:00: Start process came from the around method. The around method then ran the test producing , followed by printing 2012-01-06T15:08:42.280-06:00: End process. The test passed and we successfully established a fixture in Specs2.

Specs2 flexes its muscle with the Scala language. Eric Torreborre, the testing framework’s author, likes to be pushing the envelope and trying different things to enhance the framework and create more and more functionality for the test-driven developer. This book covers a lot of what Specs2 covers, but it doesn’t cover everything, especially since it is constantly being developed.

Which testing to framework to use? This is up to you. But the real answer is why not both? ScalaTest and Specs2 cover different things for different reasons. ScalaTest offers various specs that are clear and easy to use, and that clarity comes from a well-engineered and well-documented framework. You may find that you need to gradually get used to Scala—especially testing in Scala—and you still enjoy JUnit- and TestNG-style tests. You may also find that data tables in Specs2 come in very handy. If you wish to use ScalaMock (covered later in this book), you will really love its integration with ScalaTest. Both frameworks can run ScalaCheck (also covered later) very well too, and it’s recipes will help you decide which framework is best for you. Competition always makes its participants better, and I expect that both these frameworks will have a lot to show in the future.

Get Testing in Scala now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.