ScalaTest provides specialized utilities for complex testing scenarios including bulk collection assertions, pattern matching tests, private method testing, XML comparison, property-based testing integration, and test execution control. These advanced features enable comprehensive testing of sophisticated systems and edge cases.
Visual assertion failure reporting that shows expression evaluation trees for better debugging.
/**
* Enhanced assertion failure reporting with visual diagrams
*/
trait Diagrams {
/**
* Enhanced assert with visual failure diagrams showing expression evaluation
* @param condition boolean expression to evaluate
*/
def assert(condition: Boolean): Assertion
/**
* Enhanced assert with clue and visual failure diagrams
* @param condition boolean expression to evaluate
* @param clue additional context for failures
*/
def assert(condition: Boolean, clue: Any): Assertion
}Usage Example:
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import org.scalatest.diagrams.Diagrams
class DiagramExampleSpec extends AnyFunSuite with Matchers with Diagrams {
test("enhanced assertion failure reporting") {
val users = List(
User("Alice", 25),
User("Bob", 30),
User("Charlie", 35)
)
// When this fails, diagrams show the evaluation tree
assert(users.filter(_.age > 40).nonEmpty)
// Shows: users.filter(_.age > 40).nonEmpty was false
// users.filter(_.age > 40) was List()
// users was List(User("Alice", 25), User("Bob", 30), User("Charlie", 35))
}
test("complex expression diagrams") {
val x = 5
val y = 10
val threshold = 20
// Enhanced failure reporting shows each step
assert((x * 2) + (y / 2) > threshold)
// Shows: (x * 2) + (y / 2) > threshold was false
// (x * 2) + (y / 2) was 15
// (x * 2) was 10, (y / 2) was 5
// x was 5, y was 10, threshold was 20
}
}Apply assertions to every element in collections with detailed failure reporting.
/**
* Bulk assertion utilities for collections
*/
trait Inspectors {
/**
* Assert that all elements in collection satisfy the assertion
* @param xs the collection to inspect
* @param fun assertion applied to each element
*/
def forAll[E](xs: scala.collection.GenTraversable[E])(fun: E => Unit): Unit
/**
* Assert that at least minimum number of elements satisfy assertion
* @param min minimum number of elements that must satisfy assertion
* @param xs the collection to inspect
* @param fun assertion applied to each element
*/
def forAtLeast[E](min: Int, xs: scala.collection.GenTraversable[E])(fun: E => Unit): Unit
/**
* Assert that at most maximum number of elements satisfy assertion
* @param max maximum number of elements that can satisfy assertion
* @param xs the collection to inspect
* @param fun assertion applied to each element
*/
def forAtMost[E](max: Int, xs: scala.collection.GenTraversable[E])(fun: E => Unit): Unit
/**
* Assert that between min and max elements (inclusive) satisfy assertion
* @param from minimum number of elements (inclusive)
* @param upTo maximum number of elements (inclusive)
* @param xs the collection to inspect
* @param fun assertion applied to each element
*/
def forBetween[E](from: Int, upTo: Int, xs: scala.collection.GenTraversable[E])(fun: E => Unit): Unit
/**
* Assert that exactly the specified number of elements satisfy assertion
* @param num exact number of elements that must satisfy assertion
* @param xs the collection to inspect
* @param fun assertion applied to each element
*/
def forExactly[E](num: Int, xs: scala.collection.GenTraversable[E])(fun: E => Unit): Unit
/**
* Assert that every element in collection satisfies assertion (synonym for forAll)
* @param xs the collection to inspect
* @param fun assertion applied to each element
*/
def forEvery[E](xs: scala.collection.GenTraversable[E])(fun: E => Unit): Unit
}Usage Examples:
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import org.scalatest.Inspectors
class InspectorExampleSpec extends AnyFunSuite with Matchers with Inspectors {
test("all users should have valid data") {
val users = List(
User("Alice", 25, "alice@example.com"),
User("Bob", 30, "bob@example.com"),
User("Charlie", 35, "charlie@example.com")
)
forAll(users) { user =>
user.name should not be empty
user.age should be > 0
user.email should include("@")
}
}
test("at least half of scores should be passing") {
val scores = List(85, 92, 67, 78, 95, 88, 72)
val passingThreshold = 70
forAtLeast(scores.length / 2, scores) { score =>
score should be >= passingThreshold
}
}
test("at most two items should be expensive") {
val prices = List(10.99, 250.00, 15.50, 300.00, 25.00, 8.75)
val expensiveThreshold = 200.00
forAtMost(2, prices) { price =>
price should be > expensiveThreshold
}
}
test("between 2 and 4 words should be capitalized") {
val words = List("Hello", "world", "This", "Is", "a", "Test")
forBetween(2, 4, words) { word =>
word.head.isUpper should be(true)
}
}
test("exactly one user should be admin") {
val users = List(
User("Alice", Role.USER),
User("Bob", Role.ADMIN),
User("Charlie", Role.USER)
)
forExactly(1, users) { user =>
user.role should equal(Role.ADMIN)
}
}
test("detailed failure reporting") {
val numbers = List(1, 2, 3, 4, 5, 6)
// This will fail and show exactly which elements failed
forAll(numbers) { num =>
num should be < 5 // Will fail for 5 and 6
}
}
}Use pattern matching in assertions with detailed failure reporting.
/**
* Pattern matching assertion utilities
*/
trait Inside {
/**
* Apply pattern matching assertion to a value
* @param value the value to pattern match against
* @param pf partial function containing pattern and assertions
*/
def inside[T](value: T)(pf: PartialFunction[T, Unit]): Unit
}Usage Examples:
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import org.scalatest.Inside
class InsideExampleSpec extends AnyFunSuite with Matchers with Inside {
test("pattern matching on case classes") {
val response = ApiResponse(200, "Success", Some(UserData("Alice", 25)))
inside(response) {
case ApiResponse(status, message, Some(userData)) =>
status should equal(200)
message should equal("Success")
userData.name should equal("Alice")
userData.age should be > 18
}
}
test("pattern matching on collections") {
val numbers = List(1, 2, 3, 4, 5)
inside(numbers) {
case head :: second :: tail =>
head should equal(1)
second should equal(2)
tail should have length 3
tail should contain allOf(3, 4, 5)
}
}
test("pattern matching on nested structures") {
val json = JsonObject(Map(
"user" -> JsonObject(Map(
"name" -> JsonString("Alice"),
"age" -> JsonNumber(25)
)),
"status" -> JsonString("active")
))
inside(json) {
case JsonObject(fields) =>
inside(fields("user")) {
case JsonObject(userFields) =>
inside(userFields("name")) {
case JsonString(name) => name should equal("Alice")
}
inside(userFields("age")) {
case JsonNumber(age) => age should equal(25)
}
}
inside(fields("status")) {
case JsonString(status) => status should equal("active")
}
}
}
test("pattern matching with guards") {
val event = UserEvent("login", timestamp = System.currentTimeMillis())
inside(event) {
case UserEvent(eventType, timestamp) if timestamp > 0 =>
eventType should equal("login")
timestamp should be > 0L
// Additional assertions based on successful pattern match
}
}
}Create multiple assertion checkpoints that all must pass, with detailed reporting of which ones failed.
/**
* Multiple assertion checkpoint utilities
*/
trait Checkpoints {
/**
* Create a checkpoint with a name
* @param name descriptive name for the checkpoint
*/
def checkpoint(name: String): Checkpoint
/**
* Individual checkpoint that can be used in assertions
*/
final class Checkpoint(name: String) {
/**
* Apply assertion to this checkpoint
* @param assertion the assertion to apply
*/
def apply(assertion: => Unit): Unit
}
}Usage Examples:
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import org.scalatest.Checkpoints
class CheckpointExampleSpec extends AnyFunSuite with Matchers with Checkpoints {
test("user validation with multiple checkpoints") {
val user = User("Alice", 25, "alice@example.com", isActive = true)
val nameCheck = checkpoint("name validation")
val ageCheck = checkpoint("age validation")
val emailCheck = checkpoint("email validation")
val statusCheck = checkpoint("status validation")
nameCheck {
user.name should not be empty
user.name should have length (be >= 2 and be <= 50)
}
ageCheck {
user.age should be >= 18
user.age should be <= 120
}
emailCheck {
user.email should include("@")
user.email should include(".")
user.email should not startWith "@"
}
statusCheck {
user.isActive should be(true)
}
// If any checkpoint fails, all checkpoint results are reported
}
test("API response validation with checkpoints") {
val response = makeApiCall("/users/123")
val statusCheck = checkpoint("HTTP status")
val headersCheck = checkpoint("response headers")
val bodyCheck = checkpoint("response body")
statusCheck {
response.status should equal(200)
}
headersCheck {
response.headers should contain key "Content-Type"
response.headers("Content-Type") should include("application/json")
}
bodyCheck {
val user = parseUser(response.body)
user.id should equal(123)
user.name should not be empty
}
}
}Test private methods using reflection-based invocation.
/**
* Private method testing utilities
*/
trait PrivateMethodTester {
/**
* Create a private method invocation
* @param methodName the name of the private method (as Symbol)
* @return PrivateMethod instance for invocation
*/
def PrivateMethod[T](methodName: Symbol): PrivateMethod[T]
/**
* Private method wrapper for invocation
*/
final class PrivateMethod[T](methodName: Symbol) {
/**
* Invoke the private method
* @param target the object containing the private method
* @param args arguments to pass to the method
* @return result of the method invocation
*/
def invokeOn(target: AnyRef, args: Any*): T
}
/**
* Alternative syntax for private method invocation
* @param target the object containing the private method
* @param methodName the private method name (as Symbol)
* @param args arguments to pass to the method
* @return result of the method invocation
*/
def invokePrivate[T](target: AnyRef, methodName: Symbol, args: Any*): T
}Usage Examples:
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import org.scalatest.PrivateMethodTester
class Calculator {
def add(a: Int, b: Int): Int = a + b
private def validateInput(value: Int): Boolean = value >= 0
private def complexCalculation(x: Double, y: Double): Double = {
val intermediate = x * 2 + y / 3
math.sqrt(intermediate)
}
}
class PrivateMethodTestSpec extends AnyFunSuite with Matchers with PrivateMethodTester {
test("testing private validation method") {
val calculator = new Calculator()
val validateInput = PrivateMethod[Boolean](Symbol("validateInput"))
// Test private method with valid input
val result1 = validateInput.invokeOn(calculator, 5)
result1 should be(true)
// Test private method with invalid input
val result2 = validateInput.invokeOn(calculator, -1)
result2 should be(false)
}
test("testing private calculation method") {
val calculator = new Calculator()
val complexCalculation = PrivateMethod[Double](Symbol("complexCalculation"))
val result = complexCalculation.invokeOn(calculator, 4.0, 6.0)
result should be(3.0 +- 0.001) // sqrt(4*2 + 6/3) = sqrt(10) ≈ 3.16
}
test("alternative syntax for private method testing") {
val calculator = new Calculator()
// Using invokePrivate directly
val isValid = invokePrivate[Boolean](calculator, Symbol("validateInput"), 10)
isValid should be(true)
val calculation = invokePrivate[Double](calculator, Symbol("complexCalculation"), 1.0, 3.0)
calculation should be > 0.0
}
test("private method with multiple parameters") {
class StringProcessor {
private def processStrings(s1: String, s2: String, delimiter: String): String = {
s"$s1$delimiter$s2"
}
}
val processor = new StringProcessor()
val processStrings = PrivateMethod[String](Symbol("processStrings"))
val result = processStrings.invokeOn(processor, "Hello", "World", " - ")
result should equal("Hello - World")
}
}Specialized utilities for testing XML content with normalization and comparison.
/**
* XML testing utilities with normalization
*/
trait StreamlinedXml {
/**
* Normalize XML for comparison (whitespace, formatting)
*/
implicit def convertToXmlWrapper(xml: scala.xml.Node): XmlWrapper
final class XmlWrapper(xml: scala.xml.Node) {
/**
* Compare XML content ignoring formatting differences
*/
def ===(other: scala.xml.Node): Boolean
}
}
/**
* XML equality with normalization
*/
trait StreamlinedXmlEquality {
// Enables normalized XML comparison in assertions
}
/**
* XML normalization methods
*/
trait StreamlinedXmlNormMethods {
/**
* Normalize XML node for comparison
* @param node the XML node to normalize
* @return normalized XML node
*/
def normalizeXml(node: scala.xml.Node): scala.xml.Node
}Usage Examples:
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import org.scalatest.StreamlinedXml
import scala.xml._
class XmlTestingSpec extends AnyFunSuite with Matchers with StreamlinedXml {
test("XML comparison ignoring formatting") {
val xml1 = <user><name>Alice</name><age>25</age></user>
val xml2 =
<user>
<name>Alice</name>
<age>25</age>
</user>
// Different formatting, same content
xml1 should ===(xml2)
}
test("XML content validation") {
val userXml = <user id="123"><name>Bob</name><email>bob@example.com</email></user>
// Test XML structure and content
(userXml \ "@id").text should equal("123")
(userXml \ "name").text should equal("Bob")
(userXml \ "email").text should include("@")
}
test("complex XML comparison") {
val expected =
<response>
<status>success</status>
<data>
<users>
<user id="1">Alice</user>
<user id="2">Bob</user>
</users>
</data>
</response>
val actual = generateXmlResponse()
actual should ===(expected)
// Also test specific parts
(actual \ "status").text should equal("success")
(actual \\ "user").length should equal(2)
}
test("XML with attributes and namespaces") {
val soapEnvelope =
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<getUserResponse xmlns="http://example.com/users">
<user id="123" active="true">
<name>Charlie</name>
</user>
</getUserResponse>
</soap:Body>
</soap:Envelope>
// Test namespace handling
(soapEnvelope \ "Body" \ "getUserResponse" \ "user" \ "@id").text should equal("123")
(soapEnvelope \ "Body" \ "getUserResponse" \ "user" \ "@active").text should equal("true")
}
}Control how tests are executed including parallel execution, ordering, and failure handling.
/**
* Execute tests in parallel within the suite
*/
trait ParallelTestExecution extends OneInstancePerTest {
// Tests in this suite run in parallel
}
/**
* Randomize test execution order
*/
trait RandomTestOrder extends Suite {
// Tests execute in random order each run
}
/**
* Force sequential test execution (override parallel settings)
*/
trait Sequential extends Suite {
// Tests execute sequentially even if parallel execution is configured
}
/**
* Stop test suite execution on first failure
*/
trait StopOnFailure extends Suite {
// Suite stops executing tests after first failure
}
/**
* Cancel remaining tests after first failure
*/
trait CancelAfterFailure extends Suite {
// Remaining tests are cancelled (not failed) after first failure
}
/**
* Execute tests before nested suites
*/
trait TestsBeforeNestedSuites extends Suite {
// Tests in this suite run before any nested suites
}Usage Examples:
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import org.scalatest.{ParallelTestExecution, RandomTestOrder, StopOnFailure}
class ParallelExecutionSpec extends AnyFunSuite with Matchers with ParallelTestExecution {
test("parallel test 1") {
Thread.sleep(100)
1 + 1 should equal(2)
}
test("parallel test 2") {
Thread.sleep(50)
2 + 2 should equal(4)
}
test("parallel test 3") {
Thread.sleep(75)
3 + 3 should equal(6)
}
// These tests run concurrently
}
class RandomOrderSpec extends AnyFunSuite with Matchers with RandomTestOrder {
test("test A") {
println("Executing test A")
succeed
}
test("test B") {
println("Executing test B")
succeed
}
test("test C") {
println("Executing test C")
succeed
}
// Execution order varies between runs
}
class FailFastSpec extends AnyFunSuite with Matchers with StopOnFailure {
test("this test passes") {
1 should equal(1)
}
test("this test fails") {
fail("Intentional failure")
}
test("this test would be skipped") {
// This test won't execute due to StopOnFailure
2 should equal(2)
}
}Add runtime information, alerts, and documentation to tests.
/**
* Provide runtime information during test execution
*/
trait Informing {
/**
* Add information to test output
* @param informer the informer instance
*/
protected def info: Informer
}
trait Informer {
/**
* Provide information during test execution
* @param message information message
*/
def apply(message: String): Unit
}
/**
* Send alert messages during test execution
*/
trait Alerting {
protected def alert: Alerter
}
trait Alerter {
def apply(message: String): Unit
}
/**
* Add documentation to tests
*/
trait Documenting {
protected def markup: Documenter
}
trait Documenter {
def apply(message: String): Unit
}
/**
* BDD-style test documentation
*/
trait GivenWhenThen {
/**
* Document test preconditions
* @param message description of given conditions
*/
def Given(message: String): Unit
/**
* Document test actions
* @param message description of actions taken
*/
def When(message: String): Unit
/**
* Document expected outcomes
* @param message description of expected results
*/
def Then(message: String): Unit
/**
* Document additional conditions or actions
* @param message description of additional context
*/
def And(message: String): Unit
}Usage Examples:
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import org.scalatest.GivenWhenThen
class DocumentedTestSpec extends AnyFunSuite with Matchers with GivenWhenThen {
test("user registration process") {
Given("a new user wants to register")
val userData = UserRegistrationData("Alice", "alice@example.com", "password123")
And("the email is not already taken")
val userService = new UserService()
userService.isEmailAvailable(userData.email) should be(true)
When("the user submits registration")
val result = userService.registerUser(userData)
Then("the user should be created successfully")
result.isSuccess should be(true)
result.user.name should equal("Alice")
And("the user should receive a confirmation email")
val emailService = new EmailService()
emailService.wasConfirmationSent(result.user.email) should be(true)
info("Registration completed with user ID: " + result.user.id)
}
test("shopping cart workflow with detailed documentation") {
Given("an empty shopping cart")
val cart = new ShoppingCart()
cart.items should be(empty)
When("items are added to the cart")
val book = Product("Scala Programming", 39.99)
val pen = Product("Blue Pen", 2.50)
cart.addItem(book)
cart.addItem(pen)
Then("the cart should contain the items")
cart.items should have size 2
cart.items should contain allOf(book, pen)
And("the total should be calculated correctly")
cart.total should equal(42.49)
info(s"Cart total: ${cart.total}")
markup("This test demonstrates the basic shopping cart functionality")
}
}test("validate entire user collection") {
val users = loadUsers()
forAll(users) { user =>
inside(user) {
case User(name, age, email, profile) =>
name should not be empty
age should (be >= 18 and be <= 120)
email should fullyMatch(emailRegex)
inside(profile) {
case Some(UserProfile(bio, location)) =>
bio should have length (be <= 500)
location should not be empty
case None => // OK for profile to be empty
}
}
}
}test("validate API response structure") {
val response = callApi("/users/123")
val statusCheck = checkpoint("status validation")
val dataCheck = checkpoint("data validation")
val metaCheck = checkpoint("metadata validation")
statusCheck {
response.status should be(200)
}
inside(response.body) {
case ApiResponse(data, metadata, links) =>
dataCheck {
inside(data) {
case UserData(id, name, email, preferences) =>
id should equal(123)
name should not be empty
email should include("@")
}
}
metaCheck {
metadata.version should equal("1.0")
metadata.timestamp should be > 0L
}
}
}test("internal processing pipeline") {
val processor = new DataProcessor()
// Test individual private methods
val validateData = PrivateMethod[Boolean](Symbol("validateData"))
val transformData = PrivateMethod[ProcessedData](Symbol("transformData"))
val persistData = PrivateMethod[String](Symbol("persistData"))
val rawData = RawData("test data")
validateData.invokeOn(processor, rawData) should be(true)
val transformed = transformData.invokeOn(processor, rawData)
transformed.isValid should be(true)
val id = persistData.invokeOn(processor, transformed)
id should not be empty
}