ScalaTest Guide: The Most Popular Scala Testing Framework
ScalaTest is the most widely used testing framework in the Scala ecosystem. It supports multiple testing styles, integrates with popular mocking libraries, and works with both Scala and Java code. Whether you prefer unit tests that look like specs or simple function-based tests, ScalaTest has a style that fits.
Installation
Add ScalaTest to your build.sbt:
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.18" % TestRun tests with:
sbt test
sbt testOnly com.myapp.UserServiceSpecChoosing a Test Style
ScalaTest offers several test styles through different traits. The most common are:
AnyFlatSpec (Most Popular)
Simple, readable syntax that separates the subject from the behavior:
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class CalculatorSpec extends AnyFlatSpec with Matchers {
"Calculator" should "add two numbers" in {
val calc = new Calculator
calc.add(2, 3) should be(5)
}
it should "subtract numbers" in {
val calc = new Calculator
calc.subtract(10, 4) should be(6)
}
it should "throw an exception for division by zero" in {
val calc = new Calculator
an[ArithmeticException] should be thrownBy {
calc.divide(10, 0)
}
}
}AnyWordSpec (BDD Style)
Nested structure with describe-like blocks:
import org.scalatest.wordspec.AnyWordSpec
class UserServiceSpec extends AnyWordSpec with Matchers {
"UserService" when {
"creating a user" should {
"return the created user with an ID" in {
val service = new UserService
val user = service.create("Alice", "alice@example.com")
user.id should not be empty
user.name should be("Alice")
}
"reject duplicate emails" in {
val service = new UserService
service.create("Alice", "alice@example.com")
an[DuplicateEmailException] should be thrownBy {
service.create("Bob", "alice@example.com")
}
}
}
}
}AnyFunSuite (JUnit-like)
Familiar to developers coming from Java:
import org.scalatest.funsuite.AnyFunSuite
class StringUtilsSuite extends AnyFunSuite {
test("capitalize first letter") {
assert(StringUtils.capitalize("hello") === "Hello")
}
test("return empty string unchanged") {
assert(StringUtils.capitalize("") === "")
}
}Matchers
ScalaTest's matchers make assertions readable. Mix in Matchers to access them:
// Equality
result should be(5)
result should equal(5)
result shouldEqual 5
result should ===(5)
// Not
result should not be(0)
result should not equal("unexpected")
// Comparison
result should be > 0
result should be >= 5
result should be < 100
// Strings
str should startWith("Hello")
str should endWith("world")
str should include("elixir")
str should fullyMatch regex("\\d{4}-\\d{2}-\\d{2}")
// Collections
list should contain(42)
list should contain allOf(1, 2, 3)
list should have length 5
list shouldBe empty
list should not be empty
// Options
opt should be(defined)
opt should contain("value")Testing Exceptions
Three ways to test for exceptions:
// Assert exception is thrown
an[IllegalArgumentException] should be thrownBy {
riskyOperation(-1)
}
// Inspect the exception
val ex = the[IllegalArgumentException] thrownBy {
riskyOperation(-1)
}
ex.getMessage should include("negative")
// Functional style
intercept[ArrayIndexOutOfBoundsException] {
Array(1, 2, 3)(5)
}Fixtures
Shared state between tests is managed with fixtures. The cleanest approach for mutable state is BeforeAndAfter:
import org.scalatest.BeforeAndAfter
class DatabaseSpec extends AnyFlatSpec with Matchers with BeforeAndAfter {
var db: TestDatabase = _
before {
db = new TestDatabase
db.migrate()
}
after {
db.cleanup()
}
"Database" should "persist users" in {
db.insert(User("Alice"))
db.find("Alice") should be(defined)
}
}For functional, composable fixtures prefer fixture.Builder:
def withDatabase(test: TestDatabase => Any): Unit = {
val db = new TestDatabase
db.migrate()
try test(db)
finally db.cleanup()
}
"Database" should "persist users" in withDatabase { db =>
db.insert(User("Alice"))
db.find("Alice") should be(defined)
}Tagging Tests
Tags let you run subsets of tests:
import org.scalatest.Tag
object Slow extends Tag("com.myapp.Slow")
object Integration extends Tag("com.myapp.Integration")
class SomeSpec extends AnyFlatSpec {
"fast operation" should "complete quickly" in {
// runs always
}
"slow operation" should "process large dataset" taggedAs Slow in {
// excluded with -l com.myapp.Slow
}
}Run excluding slow tests:
sbt "testOnly * -- -l com.myapp.Slow"Async Testing
For testing Future-based code, use AsyncFlatSpec:
import org.scalatest.flatspec.AsyncFlatSpec
import scala.concurrent.Future
class UserServiceAsyncSpec extends AsyncFlatSpec with Matchers {
"UserService" should "fetch user asynchronously" in {
val service = new UserService
service.findById(42).map { user =>
user.name should be("Alice")
user.id should be(42)
}
}
it should "return failed future for unknown user" in {
val service = new UserService
recoverToSucceededIf[UserNotFoundException] {
service.findById(9999)
}
}
}Running Specific Tests
# Run all tests
sbt <span class="hljs-built_in">test
<span class="hljs-comment"># Run a specific spec
sbt <span class="hljs-string">"testOnly com.myapp.UserServiceSpec"
<span class="hljs-comment"># Run specs matching a pattern
sbt <span class="hljs-string">"testOnly *Spec"
<span class="hljs-comment"># Run only tests with a specific tag
sbt <span class="hljs-string">"testOnly * -- -n com.myapp.Integration"
<span class="hljs-comment"># Run continuously (watch mode)
sbt ~<span class="hljs-built_in">testParallel Execution
Configure parallel test execution in build.sbt:
// Run test suites in parallel
Test / parallelExecution := true
// But run tests within a suite sequentially (default)Or per-suite with ParallelTestExecution:
class MyParallelSpec extends AnyFlatSpec
with Matchers
with ParallelTestExecution {
// Tests in this suite run in parallel
"service" should "handle concurrent requests" in { ... }
}Testing Full User Flows
ScalaTest handles backend logic well. For Scala web applications (Play Framework, http4s), verifying complete user flows in a browser requires a different tool. HelpMeTest runs Robot Framework tests against your live application, monitoring continuously and alerting on failures — useful for catching integration issues between your Scala services and their front-ends.
Summary
ScalaTest gives you flexibility to match your team's testing style:
- AnyFlatSpec for readable, behavior-focused specs
- AnyWordSpec for BDD-style nested descriptions
- AnyFunSuite for JUnit-familiar syntax
- Rich matchers for expressive assertions
- AsyncFlatSpec for Future-based testing
- BeforeAndAfter / fixture builders for shared setup/teardown
Start with AnyFlatSpec with Matchers — it's the most widely adopted combination, and your tests will look immediately familiar to other Scala developers.