Scala Testing: ScalaTest and Property-Based Testing
Scala Testing: ScalaTest and Property-Based Testing
Testing in Scala combines familiar unit testing patterns with powerful functional testing tools. ScalaTest provides multiple testing styles, ScalaCheck brings property-based testing, and Mockito handles mocking. This module covers the full testing toolkit for Scala applications.
Setting Up ScalaTest
// build.sbt
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.2.17" % Test,
"org.mockito" %% "mockito-scala" % "1.17.30" % Test,
"org.scalacheck" %% "scalacheck" % "1.17.0" % Test
)Run tests with sbt test or sbt testOnly *MySpec.
FunSuite: Simple Test Cases
FunSuite is the closest to JUnit — each test is a named function:
import org.scalatest.funsuite.AnyFunSuite
class CalculatorSuite extends AnyFunSuite {
test("addition works correctly") {
assert(2 + 2 == 4)
}
test("division throws on zero denominator") {
assertThrows[ArithmeticException] {
10 / 0
}
}
test("string concatenation") {
val result = "Hello" + " " + "World"
assert(result == "Hello World")
assert(result.length == 11)
}
}FlatSpec: BDD-Style Tests
FlatSpec reads like a specification:
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class StringSpec extends AnyFlatSpec with Matchers {
"A String" should "support length calculation" in {
"hello".length should be(5)
}
it should "support uppercase conversion" in {
"hello".toUpperCase should equal("HELLO")
}
it should "support contains check" in {
"hello world" should include("world")
}
"An empty string" should "have length zero" in {
"".length should be(0)
"" should be(empty)
}
}Matchers: Readable Assertions
The Matchers trait gives you expressive assertions:
import org.scalatest.matchers.should.Matchers
// Equality
result should equal(42)
result shouldBe 42
result should not equal 0
// Comparisons
value should be > 0
value should be <= 100
// Collections
list should have length 3
list should contain(42)
list should contain allOf(1, 2, 3)
list should be(empty)
list should not be empty
// Strings
str should startWith("Hello")
str should endWith("World")
str should include("lo W")
str should fullyMatch regex "[A-Z].*"
// Options
option should be(defined)
option should be(None)
option should contain(42)
// Exceptions
an [IllegalArgumentException] should be thrownBy {
require(false, "bad input")
}WordSpec: Full BDD Specification
WordSpec provides the most structured BDD format:
import org.scalatest.wordspec.AnyWordSpec
import org.scalatest.matchers.should.Matchers
class UserServiceSpec extends AnyWordSpec with Matchers {
"UserService" when {
"creating a new user" should {
"return a User with the given name" in {
// test body
val user = User("Alice", 30)
user.name shouldBe "Alice"
}
"assign a unique ID" in {
val u1 = User("Alice", 30)
val u2 = User("Bob", 25)
u1.id should not equal u2.id
}
}
"finding an existing user" should {
"return Some(user) when found" in {
// ...
}
"return None when not found" in {
// ...
}
}
}
}BeforeAndAfter: Setup and Teardown
import org.scalatest.BeforeAndAfter
import org.scalatest.flatspec.AnyFlatSpec
class DatabaseSpec extends AnyFlatSpec with BeforeAndAfter {
var connection: Connection = _
before {
connection = openTestConnection()
}
after {
connection.close()
}
"Database" should "insert and retrieve records" in {
// use connection
}
}Mocking with Mockito for Scala
import org.mockito.MockitoSugar
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
trait UserRepository {
def findById(id: Int): Option[User]
def save(user: User): Unit
}
class UserServiceSpec extends AnyFlatSpec
with Matchers
with MockitoSugar {
"UserService" should "return user when found" in {
val mockRepo = mock[UserRepository]
val service = new UserService(mockRepo)
when(mockRepo.findById(1)).thenReturn(Some(User("Alice", 30)))
val result = service.getUser(1)
result shouldBe Some(User("Alice", 30))
verify(mockRepo).findById(1)
}
it should "return None when user not found" in {
val mockRepo = mock[UserRepository]
val service = new UserService(mockRepo)
when(mockRepo.findById(99)).thenReturn(None)
service.getUser(99) shouldBe None
}
}Property-Based Testing with ScalaCheck
Instead of writing specific examples, ScalaCheck generates random test cases:
import org.scalacheck.{Gen, Properties}
import org.scalacheck.Prop.forAll
object StringProps extends Properties("String") {
property("reverse twice is identity") = forAll { (s: String) =>
s.reverse.reverse == s
}
property("concatenation length") = forAll { (s1: String, s2: String) =>
(s1 + s2).length == s1.length + s2.length
}
}
// Using ScalaCheck within ScalaTest
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
import org.scalatest.flatspec.AnyFlatSpec
class ListSpec extends AnyFlatSpec with ScalaCheckPropertyChecks {
"List.reverse" should "be its own inverse" in {
forAll { (list: List[Int]) =>
list.reverse.reverse shouldBe list
}
}
it should "preserve elements" in {
forAll { (list: List[Int]) =>
list.reverse.sorted shouldBe list.sorted
}
}
}ScalaCheck tries to find a counter-example. If it fails, it "shrinks" the input to the smallest failing case.
Custom Generators
import org.scalacheck.Gen
val positiveInt: Gen[Int] = Gen.posNum[Int]
val nonEmptyString: Gen[String] = Gen.alphaStr.suchThat(_.nonEmpty)
case class Person(name: String, age: Int)
val personGen: Gen[Person] = for {
name <- Gen.alphaStr.suchThat(_.nonEmpty)
age <- Gen.chooseNum(0, 120)
} yield Person(name, age)
forAll(personGen) { person =>
person.age should be >= 0
person.name should not be empty
}Frequently Asked Questions
Q: Which ScalaTest style should I choose — FunSuite, FlatSpec, or WordSpec? FunSuite is best for beginners and teams coming from JUnit — it's familiar and minimal. FlatSpec is popular for library code where tests read as specifications. WordSpec is preferred when writing acceptance-level tests or behavior-driven development (BDD) where the full "given/when/then" structure matters. Pick one style per project and be consistent. If in doubt, start with FlatSpec — it's concise and readable.
Q: What is the main benefit of property-based testing over example-based testing? Example-based tests check specific cases you thought of. Property-based tests check invariants that should hold for all inputs in a domain — and the framework generates hundreds of random inputs to try to break them. Properties often find edge cases you never considered: empty strings, negative numbers, unicode characters, very long inputs. They're especially valuable for pure functions, data transformations, and any code with mathematical invariants.
Q: How do I test Futures in ScalaTest?
Use ScalaFutures trait: import org.scalatest.concurrent.ScalaFutures and call whenReady(future) { result => ... } or use futureValue to block and unwrap. Set a PatienceConfig to control how long to wait. For Akka applications, use akka-testkit which provides TestActorRef, TestProbe, and utilities to test actors synchronously.
Part of Scala Mastery Course — Module 21 of 22.
