Published 2014-01-14.
Last modified 2017-01-30.
Time to read: 7 minutes.
Scala has two popular unit testing frameworks: ScalaTest and Specs2. We explore the common aspects of both test frameworks in this lecture. The unit tests in this lecture run from the command line, using IntelliJ IDEA versions 13 onwards on Ubuntu, Mac OS/X and Windows using Scala 2.12+. Visual Studio Code is also supported.
ScalaTest and Specs2 are both comprehensive test frameworks.
This lecture is merely intended to give you an introduction to common aspects of these test frameworks.
Both of these unit testing frameworks are much more powerful than JUnit
,
however they interoperate with JUnit
and can use the JUnit
test runner.
A nice feature of both frameworks is the ability to write unit tests using a DSL.
One way that projects gradually introduce Scala into an all-Java code base is to write unit tests using one of these frameworks.
SBT follows the Maven convention of placing unit test source files in the project’s src/test
folder.
You can use SBT to run them from the command line using the test
, testOnly
and testQuick
tasks.
IntelliJ IDEA and Visual Studio Code both offer integrated unit test support.
You cannot use the REPL, Scala scripts or worksheets to write unit tests.
Project Setup
Specs2
Specs2 for Scala 2.11 and 2.12 can be specified for a build.sbt
file like this:
libraryDependencies ++= Seq( "org.specs2" %% "specs2-core" % "3.8.6" % Test, "org.specs2" %% "specs2-junit" % "3.8.6" % Test )
Dependencies that have % "test"
or % Test
following the dependency versions will only be included by SBT when running unit tests.
ScalaTest
ScalaTest can be included into a build.sbt
file like this:
libraryDependencies ++= Seq ( "org.scalatest" %% "scalatest" % "3.0.0" % Test )
Again, notice the % Test
that follows the dependency names.
ScalaTest and Specs2 In the Same Project
You can add dependencies for both Specs2 and ScalaTest in the same build.sbt
file.
The necessary libraryDependencies
are:
libraryDependencies ++= Seq( "org.specs2" %% "specs2-core" % "3.8.6" % Test, "org.specs2" %% "specs2-junit" % "3.8.6" % Test, "org.scalatest" %% "scalatest" % "3.0.0" % Test, )
Because these dependencies are only required for tests, they do not add overhead to your deployed project.
Quick and Easy Test Specifications
This lecture demonstrates unit tests written for both frameworks using similar DSLs.
With these DSLs, test classes contain should
or must
statements which themselves contain one or more in
statements.
If a problem occurs, the error messages are quite helpful.
Place your unit tests in the test/scala or test/java directories.
Tests written with these DSLs read like:
The MumbleFratz should obliviate the FramminJammin in { /* unit tests here */ }
Specs2
This file is provided in courseNotes/src/test/scala/Specs2Demo.scala
.
Two of the imports are specified just so the JUnit test runner can be used.
The @RunWith
annotation is so Scala-IDE can run the tests.
The test class extends Specification
, which defines the DSL for writing unit tests.
Three unit tests are contained within this Specification
.
The Specification
DSL groups unit tests by blocks of code that start with a descriptive String
followed by the word
should
or must
, followed by zero or more unit tests contained within curly braces.
This Specification
instance contains one should
container that reads something like:
"The 'Hello world' string" should { /* unit tests here */ }
Each unit test starts with a descriptive String
followed by the word in
,
followed by the body of the unit test contained within curly braces.
The words should
, must
and in
are actually method calls.
import org.specs2.mutable._ import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner]) class Specs2Demo extends Specification { "The ’Hello world’ string" should { "contain 11 characters" in { "Hello world" must have size 11 }
"start with ’Hello’" in { "Hello world" must startWith("Hello") }
"end with ’world’" in { "Hello world" must endWith("world") } } }
You read the unit tests starting with the outer group description and appending the unit test description, followed by the body of the unit test which implements the test. For example, these unit tests are read this way:
-
The "Hello world" string should contain 11 characters
–
followed by the DSL which implements the test:
"Hello world" must have size 11
-
The "Hello world" stri
–ng should
start with "Hello" in
followed by the DSL which implements the test:"Hello world" must startWith("Hello")
-
The "Hello world" string should end with "world" in
–
followed by the DSL which implements the test:
"Hello world" must endWith("world")
See the Specs2 documentation for more information.
ScalaTest
ScalaTest is extremely flexible in how unit tests can be structured and offers several DSLs:
FeatureSpec
,
FlatSpec
,
FreeSpec
,
FunSpec
,
PropSpec
, and
WordSpec
.
This example was intended to resemble the Specs2 unit test above as closely as possible,
so this test class extends WordSpec
,
which provides a DSL for writing unit tests that is quite similar to Specs2’s Specification
.
The sample code for this lecture can be found in
courseNotes/
.
Notice that:
- The structure of the unit tests are almost identical for the Specs2
Specification
and ScalaTestWordSpec
DSLs – the only difference is how the body of the unit tests are actually written. WordSpec
uses the wordshould
instead ofmust
.- Triple equals (===) is used to assert that two values must be equal.
import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner import org.scalatest._ import org.scalatest.Matchers._
@RunWith(classOf[JUnitRunner]) class TestScalaTest extends WordSpec { "The ’Hello world’ string" should { "contain 11 characters" in { "Hello world".length === 11 }
"start with ’Hello’" in { "Hello world" should startWith("Hello") }
"end with ’world’" in { "Hello world" should endWith("world") } } }
The ScalaTest documentation discusses additional test types. For more information.
Lets look at some specific ScalaTest features now.
Testing Strings
The ScalaTest DSL is quirky; the syntax changes depending on whether there are an even or odd number of words used in the comparison.
You can test
all the things you might expect:
equality, length, startsWith
, endsWith
,
if a substring is present in (include
), and
regular expressions (regexes).
"Strings" should { val string = """Thank you for your order. |You enrolled in the best course ever! |Go study and become successful. |""".stripMargin "compare normally" in { string === string string.length shouldBe 96 string should startWith("Thank you for your order") string should include("the best course ever!") string should endWith("successful.\n") } "compare with regexes" in { string should fullyMatch regex "(?s)Thank .* best .*" string should startWith regex "Thank .*" string should include regex "best course .*" string should endWith regex "successful.\n" } }
We can also use regular expressions to compare strings.
- Strings can fully match a regex by using
fullyMatch
:Thestring should fullyMatch regex "(?s)Thank .* best .*"
(?s)
inside the regex enables the DOTALL flag, which tells the regex engine built into the JVM to treat newlines as regular characters. - Strings can be tested to ensure that the beginning of the string matches a regular expression:
string should startWith regex "Thank .*"
- Strings can be tested to ensure that they contain a substring that matches a regex:
string should include regex "best course .*"
- Strings can be tested to ensure that the end of the string matches a regular expression:
string should endWith regex "Thank .*"
Working With OptionValues
The ScalaTest documentation describes a trait called
org.scalatest.OptionValues
that makes working with Option
values more convenient.
The OptionValues
companion object
is documented in the ScalaTest Scaladoc;
this object allows you to import
the OptionValues
behavior instead of mixing in the OptionValues
trait into the test class.
The OptionValues
import is automatically performed if you import org.scalatest.Matchers._
.
Let’s look at the following code.
"OptionValue" should { "work for Some values" in { val option = Some(3) option shouldBe defined option.value shouldBe 3 option.value should be < 7 option should contain oneOf (3, 5, 7, 9) List(3, 5, 7, 9) should contain (option.value) option should not contain oneOf (7, 8, 9) List(5, 7, 9) should not contain option.value option should contain noneOf (7, 8, 9) }
"work for None" in { val option: Option[Int] = None // the following are all equivalent: option shouldEqual None option shouldBe None option should === (None) option shouldBe empty } }
The first test, "OptionValue should work for Some values"
, defines an immutable variable called option
,
of type Option[Int]
with value Some(3)
.
-
This verifies that
option
isdefined
, or in other words that theOption
container is not empty. Notice that ScalaTest DSL requiresshouldBe
to be written as one word.ScalaTest shouldBe example 1option shouldBe defined
-
This verifies that the value of
option
must be exactly 3.ScalaTest shouldBe example 2option.value shouldBe 3
-
This verifies that the value of the option is less than 7.
The value of the
Option
container is compared to the test value by writingoption.value
. In this case, the ScalaTest DSL requiresshould be
to be written as two words. I find it hard to remember when to writeshouldBe
as one word orshould be
as two words, but my IDE tells me when I guess wrong.option.value should be < 7
-
This verifies that the option value appears in the sequence 3, 5, 7, or 9, expressed as literals using the ScalaTest DSL.
The value of the
Option
container is compared to the test values by writingoption
, without qualifying with.value
.option should contain oneOf (3, 5, 7, 9)
-
This does the same test, but shows the syntax necessary in order to work with a collection instead of a literal sequence.
Notice the parentheses around
(option.value)
, and that the.value
qualifier was required.List(3, 5, 7, 9) should contain (option.value)
-
This does the opposite test.
This time the
.value
qualifier was not required.option should not contain oneOf (7, 8, 9)
-
This again does the opposite test, but shows the syntax necessary in order to work with a collection instead of a literal sequence.
Notice there is no parentheses around
option.value
.List(3, 5, 7, 9) should not contain option.value
-
This shows the syntax necessary to ensure that the option value is not in the sequence of values.
option should contain noneOf (7, 8, 9)
The second test, "OptionValue should work for None"
, could have also been written any of these ways.
option shouldEqual None option shouldBe None option should === (None) option shouldBe empty
Working with EitherValues
The ScalaTest documentation describes a trait called
org.scalatest.EitherValues
that makes working with Either
values more convenient.
The EitherValue
s object is documented in the
ScalaTest Scaladoc; this object allows you to import
the EitherValue
s behavior instead of mixing in the
EitherValue
s trait into the test class.
Unlike the OptionValues
import, the EitherValues
import is NOT automatically performed if you
import org.scalatest.Matchers._
.
Let’s look at the following code.
"EitherValues" should { import org.scalatest.EitherValues._ val either: Either[String, Int] = Right(3) "work for Right values" in { either.right.value shouldBe 3 either shouldBe right either should not be left } }
Again we find that the ScalaTest DSL is quirky when working with Either
values.
First we define an immutable variable called either of type Either[String, Int]
,
and give it the value Right(3)
.
The test "EitherValues should work for Right values"
verifies the value of the either
variable.
-
This verifies that the variable is a
Right
and that its value is 3.ScalaTest either exampleeither.right.value shouldBe 3
-
This simply verifies that the variable is a
Right
.ScalaTest either exampleeither shouldBe ’right
-
This simply verifies that the variable is not a
Left
.ScalaTest either exampleeither should not be ’left
Testing a Value Against Multiple Predicates
You can test a value against multiple predicates by enclosing them within parentheses and combining
them with and
and or
.
"Multiple predicates" should { "combine" in { val string = """Thank you for your order. |You enrolled in the best course ever! |Go study and become successful. |""".stripMargin string should ( include("Thank you for your order") and include("You enrolled in") ) string should ( include("Thank you for your order") or include("You enrolled in") ) } }
Running Unit Tests
You can run unit tests in three environments:
- From the command line using SBT
- From IntelliJ IDEA’s test runners
- From Visual Studio Code’s test runners
We will explore each of these now.
SBT
SBT has a good test runner.
You can run all unit tests by using the sbt test
command, as shown below.
Recall that the leading tilde (~
) causes the tests to be rerun any time a source file is changed.
$ sbt ~test [info] Loading global plugins from /home/mslinn/.sbt/plugins [info] Loading project definition from /var/work/course_scala_intro_code/courseNotes/project [info] Set current project to scalaIntroCourse (in build file:/var/work/course_scala_intro_code/courseNotes/) [success] Total time: 33 s, completed Sep 2, 2013 5:14:36 PM 1. Waiting for source changes... (press enter to interrupt)
You can also just run a specific test class, like this (note the quotes):
$ sbt ~"testOnly ScalaTestDemo" [info] Loading global plugins from /home/mslinn/.sbt/plugins [info] Loading project definition from /var/work/course_scala_intro_code/courseNotes/project [info] Set current project to scalaIntroCourse (in build file:/var/work/course_scala_intro_code/courseNotes/) [success] Total time: 2 s, completed Sep 2, 2013 5:16:42 PM 1. Waiting for source changes... (press enter to interrupt)
You can also run a specific unit test within a test class by using the -z
argument within sbt
:
$ sbt [info] Loading global plugins from /home/mslinn/.sbt/0.13/plugins [info] Loading project definition from /var/work/course_scala_intro_code/courseNotes/project [info] Set current project to scalaIntroCourse (in build file:/var/work/course_scala_intro_code/courseNotes/) scala> testOnly ScalaTestDemo -z "The ’Hello world’ string should contain 11 characters" [info] Updating {file:/var/work/course_scala_intro_code/courseNotes/}coursenotes... [info] Resolving org.ccil.cowan.tagsoup#tagsoup;1.2 ... [info] Done updating. [info] Compiling 4 Scala sources to /var/work/course_scala_intro_code/courseNotes/target/scala-2.11/test-classes... [info] ScalaTestDemo: [info] The "Hello world" string [info] - should contain 11 characters [info] - should start with "Hello" [info] - should end with "world" [info] ScalaCheck [info] Passed: Total 0, Failed 0, Errors 0, Passed 0 [info] ScalaTest [info] Run completed in 203 milliseconds. [info] Total number of tests run: 3 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 3, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. [info] Passed: Total 3, Failed 0, Errors 0, Passed 3 [success] Total time: 5 s, completed Nov 28, 2014 9:25:20 PM
Notice how the test name was constructed: the name of the should
clause was concatenated with should
,
followed by the name of the in
clause, to form the full test name:
"The ’Hello world’ string should contain 11 characters"
.
Here is how you can specify all of this on one line.
We discussed this syntax in the .sbtrc
section of the
SBT Global Setup lecture.
Notice that the embedded quotes are escaped:
$ sbt ";testOnly ScalaTestDemo -z \"The ’Hello world’ string should contain 11 characters\"" [info] Loading global plugins from /home/mslinn/.sbt/0.13/plugins [info] Loading project definition from /var/work/course_scala_intro_code/courseNotes/project [info] Set current project to IntroScalaCourse (in build file:/var/work/course_scala_intro_code/courseNotes/) [info] Updating {file:/var/work/course_scala_intro_code/courseNotes/}coursenotes... [info] Resolving org.ccil.cowan.tagsoup#tagsoup;1.2 ... [info] Done updating. [info] Compiling 4 Scala sources to /var/work/course_scala_intro_code/courseNotes/target/scala-2.11/test-classes... [info] ScalaTestDemo: [info] The "Hello world" string [info] - should contain 11 characters [info] - should start with "Hello" [info] - should end with "world" [info] ScalaCheck [info] Passed: Total 0, Failed 0, Errors 0, Passed 0 [info] ScalaTest [info] Run completed in 203 milliseconds. [info] Total number of tests run: 3 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 3, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. [info] Passed: Total 3, Failed 0, Errors 0, Passed 3 [success] Total time: 8 s, completed Jan 26, 2016 11:22:51 AM
A leading tilde in combination with testQuick
causes only those tests affected by the most recently
modified change to be recompiled and tested.
This is a very useful incantation.
$ sbt ~testQuick [info] Loading global plugins from /home/mslinn/.sbt/plugins [info] Loading project definition from /var/work/course_scala_intro_code/courseNotes/project [info] Set current project to scalaIntroCourse (in build file:/var/work/course_scala_intro_code/courseNotes/) [success] Total time: 2 s, completed Sep 2, 2013 5:16:42 PM 1. Waiting for source changes... (press enter to interrupt)
IntelliJ IDEA
Running ScalaTest and Specs 2 unit tests with IDEA is easy. Simply right-click on the unit test, and select a runner. You can see the name of the test runner in the lower left corner of the window when it is selected in the popup menu. IDEA creates a run configuration based on your selection and launches the test. Here is what IDEA looks like after running the unit tests in this lecture.

Exercise – Write Specs2 and ScalaTest Unit Tests
- Create a new SBT project.
-
Write a unit test using both Specs2 and ScalaTest that fetches the contents of
https://scalacourses.
and does a case-insensitive search that verifies the wordcom scala
is present. - Use any IDE, or no IDE.
Hints
-
You can read the contents of a URL as follows:
Scala code
io.Source.fromURL("https://scalacourses.com").getLines.mkString
-
You can convert a
String
to lower case via theString.toLowerCase
method.
Solutions
The ScalaTest solution is provided in courseNotes/test/scala/solutions/ScalaTestSolution.scala
.
package solutions
import org.scalatest._
class ScalaTestSolution extends WordSpec { "ScalaCourses.com" should { "contain the word scala" in { val contents = io.Source.fromURL("https://scalacourses.com").getLines.mkString contents.toLowerCase.contains("scala") } } }
Run it like this:
$ sbt "testOnly solutions.ScalaTestSolution"
The Specs2 solution is provided in
courseNotes/
.
package solutions
import org.specs2.mutable._
class Specs2Solution extends Specification { "ScalaCourses.com" should { "contain the word scala" in { val contents = io.Source.fromURL("https://scalacourses.com").getLines().mkString contents.toLowerCase must contain("scala") } } }
Run it like this:
$ sbt "testOnly solutions.Specs2Solution"
© Copyright 1994-2024 Michael Slinn. All rights reserved.
If you would like to request to use this copyright-protected work in any manner,
please send an email.
This website was made using Jekyll and Mike Slinn’s Jekyll Plugins.