ScalaTest is a comprehensive testing framework for Scala and Java that provides multiple testing styles and sophisticated matcher libraries.
—
Scalactic provides functional programming utilities and better error handling mechanisms for Scala applications. The library focuses on functional idioms, type-safe error handling with the Or type, and pluggable equality systems.
Railway-oriented programming with Good/Bad disjunction type for error handling.
import org.scalactic._
// Core Or type
sealed abstract class Or[+G, +B] {
// Type checking methods
def isGood: Boolean
def isBad: Boolean
// Value extraction (unsafe)
def get: G // Throws if Bad
// Safe value extraction
def getOrElse[H >: G](alternative: => H): H
// Transformation methods
def map[H](f: G => H): H Or B
def flatMap[H, C >: B](f: G => H Or C): H Or C
def filter[C >: B](p: G => Boolean): G Or (B Or C)
def foreach(f: G => Unit): Unit
def fold[V](fa: B => V, fb: G => V): V
// Collection-like operations
def toOption: Option[G]
def toSeq: Seq[G]
def iterator: Iterator[G]
// Combination methods
def orElse[H >: G, C >: B](alternative: => H Or C): H Or C
def recover[H >: G](pf: PartialFunction[B, H]): H Or B
def recoverWith[H >: G, C >: B](pf: PartialFunction[B, H Or C]): H Or C
// Validation methods
def badMap[C](f: B => C): G Or C
def swap: B Or G
def exists(p: G => Boolean): Boolean
def forall(p: G => Boolean): Boolean
}
// Concrete implementations
case class Good[+G](value: G) extends Or[G, Nothing]
case class Bad[+B](value: B) extends Or[Nothing, B]
// Companion object methods
object Or {
def apply[G, B](value: G): G Or B = Good(value)
def good[G, B](value: G): G Or B = Good(value)
def bad[G, B](value: B): G Or B = Bad(value)
}Or Type Examples:
import org.scalactic._
// Basic usage
def divide(x: Int, y: Int): Int Or String = {
if (y == 0) Bad("Cannot divide by zero")
else Good(x / y)
}
val result1 = divide(10, 2) // Good(5)
val result2 = divide(10, 0) // Bad("Cannot divide by zero")
// Pattern matching
result1 match {
case Good(value) => println(s"Result: $value")
case Bad(error) => println(s"Error: $error")
}
// Functional operations
val doubled = result1.map(_ * 2) // Good(10)
val recovered = result2.getOrElse(-1) // -1
// Chaining operations
def parseAndDivide(xStr: String, yStr: String): Int Or String = {
for {
x <- parseIntSafely(xStr)
y <- parseIntSafely(yStr)
result <- divide(x, y)
} yield result
}
def parseIntSafely(str: String): Int Or String = {
try Good(str.toInt)
catch { case _: NumberFormatException => Bad(s"Not a valid integer: $str") }
}Safe execution wrapper that catches exceptions and returns Or.
import org.scalactic._
/**
* Returns the result of evaluating the given block f, wrapped in a Good, or
* if an exception is thrown, the Throwable, wrapped in a Bad.
*/
def attempt[R](f: => R): R Or ThrowableAttempt Examples:
import org.scalactic._
// Safe division
val result1 = attempt { 10 / 2 } // Good(5)
val result2 = attempt { 10 / 0 } // Bad(ArithmeticException)
// Safe file operations
def readFileSafely(filename: String): String Or Throwable = {
attempt {
scala.io.Source.fromFile(filename).mkString
}
}
// Chaining with other Or operations
val processedResult = for {
content <- readFileSafely("data.txt")
lines = content.split("\n")
firstLine <- if (lines.nonEmpty) Good(lines.head) else Bad("File is empty")
} yield firstLine.toUpperCasePluggable equality for custom comparison logic.
import org.scalactic._
// Core equality trait
trait Equality[T] {
def areEqual(a: T, b: Any): Boolean
}
// Normalization trait
trait Uniformity[T] extends Equality[T] {
def normalized(a: T): T
def normalizedCanHandle(b: Any): Boolean
def normalizedOrSame(b: Any): Any
}
// Explicitly object for controlling implicit conversions
object Explicitly {
val decided: Decided.type = Decided
val after: After.type = After
}
// Custom equality examples
implicit val stringEquality = new Equality[String] {
def areEqual(a: String, b: Any): Boolean =
b match {
case s: String => a.toLowerCase == s.toLowerCase
case _ => false
}
}Equality Examples:
import org.scalactic._
import org.scalactic.StringNormalizations._
// Case-insensitive string equality
implicit val caseInsensitiveEquality = new Equality[String] {
def areEqual(a: String, b: Any): Boolean =
b match {
case s: String => a.toLowerCase == s.toLowerCase
case _ => false
}
}
// Using with ScalaTest matchers
"Hello" should equal("HELLO")(decided by caseInsensitiveEquality)
// Normalization-based equality
implicit val trimmedStringEquality = StringNormalizations.trimmed
" hello " should equal("hello")(after being trimmed)
// Custom numeric tolerance
implicit val doubleEquality = TolerantNumerics.tolerantDoubleEquality(0.01)
3.14159 should equal(3.14)(decided by doubleEquality)Type-safe wrappers for constrained values.
import org.scalactic.anyvals._
// Positive integers
final class PosInt private (val value: Int) extends AnyVal
object PosInt {
def from(value: Int): PosInt Or One[ErrorMessage]
def ensuringValid(value: Int): PosInt
}
// Non-zero integers
final class NonZeroInt private (val value: Int) extends AnyVal
object NonZeroInt {
def from(value: Int): NonZeroInt Or One[ErrorMessage]
def ensuringValid(value: Int): NonZeroInt
}
// Positive doubles
final class PosDouble private (val value: Double) extends AnyVal
object PosDouble {
def from(value: Double): PosDouble Or One[ErrorMessage]
def ensuringValid(value: Double): PosDouble
}
// Non-empty strings
final class NonEmptyString private (val value: String) extends AnyVal
object NonEmptyString {
def from(value: String): NonEmptyString Or One[ErrorMessage]
def ensuringValid(value: String): NonEmptyString
}
// Non-empty collections
final class NonEmptyList[+T] private (val value: List[T]) extends AnyVal
object NonEmptyList {
def from[T](value: List[T]): NonEmptyList[T] Or One[ErrorMessage]
def apply[T](head: T, tail: T*): NonEmptyList[T]
}Validated Types Examples:
import org.scalactic.anyvals._
// Safe construction with validation
val posInt1 = PosInt.from(5) // Good(PosInt(5))
val posInt2 = PosInt.from(-5) // Bad(One("-5 was not greater than 0"))
// Unsafe construction (throws on invalid)
val posInt3 = PosInt.ensuringValid(10) // PosInt(10)
// Using in function parameters
def calculateArea(width: PosDouble, height: PosDouble): Double = {
width.value * height.value
}
val width = PosDouble.from(5.0).getOrElse(PosDouble.ensuringValid(1.0))
val height = PosDouble.from(3.0).getOrElse(PosDouble.ensuringValid(1.0))
val area = calculateArea(width, height)
// Non-empty collections
val nonEmptyList = NonEmptyList(1, 2, 3, 4) // NonEmptyList(List(1, 2, 3, 4))
val fromList = NonEmptyList.from(List(1, 2)) // Good(NonEmptyList(List(1, 2)))
val fromEmpty = NonEmptyList.from(List.empty) // Bad(One("List was empty"))
// Working with validated strings
def processName(name: NonEmptyString): String = {
s"Hello, ${name.value}!"
}
NonEmptyString.from("Alice") match {
case Good(name) => processName(name)
case Bad(error) => s"Invalid name: ${error.head}"
}Design by contract utilities for preconditions and postconditions.
import org.scalactic._
object Requirements {
// Precondition checking
def require(condition: Boolean): Unit
def require(condition: Boolean, message: => Any): Unit
// Null checking
def requireNonNull(reference: AnyRef): Unit
def requireNonNull(reference: AnyRef, message: => Any): Unit
// State checking
def requireState(condition: Boolean): Unit
def requireState(condition: Boolean, message: => Any): Unit
}
// Chain type (deprecated, use NonEmptyList)
@deprecated("Use NonEmptyList instead")
type Chain[+T] = NonEmptyList[T]Requirements Examples:
import org.scalactic.Requirements._
class BankAccount(initialBalance: Double) {
require(initialBalance >= 0, "Initial balance cannot be negative")
private var balance = initialBalance
def withdraw(amount: Double): Unit = {
require(amount > 0, "Withdrawal amount must be positive")
requireState(balance >= amount, "Insufficient funds")
balance -= amount
}
def deposit(amount: Double): Unit = {
require(amount > 0, "Deposit amount must be positive")
balance += amount
}
def getBalance: Double = balance
}
// Usage
val account = new BankAccount(100.0)
account.deposit(50.0) // OK
account.withdraw(75.0) // OK
// account.withdraw(100.0) // Would throw IllegalStateExceptionEnhanced error messages and value formatting.
import org.scalactic._
// Pretty printing customization
trait Prettifier {
def apply(o: Any): String
}
object Prettifier {
val default: Prettifier
val truncateAt: Int => Prettifier
val lineSeparator: String => Prettifier
}
// Position tracking for better error messages
package object source {
case class Position(fileName: String, filePath: String, lineNumber: Int)
implicit def here: Position = macro PositionMacro.genPosition
}Additional collection operations and utilities.
// Every type for expressing "all elements match"
final class Every[+T] private (underlying: Vector[T]) {
def map[U](f: T => U): Every[U]
def flatMap[U](f: T => Every[U]): Every[U]
def filter(p: T => Boolean): Seq[T]
def exists(p: T => Boolean): Boolean
def forall(p: T => Boolean): Boolean
def toVector: Vector[T]
def toList: List[T]
def toSeq: Seq[T]
}
object Every {
def apply[T](firstElement: T, otherElements: T*): Every[T]
def from[T](seq: Seq[T]): Every[T] Or One[ErrorMessage]
}
// One type for single-element collections
final class One[+T](element: T) {
def map[U](f: T => U): One[U]
def flatMap[U](f: T => One[U]): One[U]
def head: T
def toSeq: Seq[T]
def toList: List[T]
def toVector: Vector[T]
}package object scalactic {
// Type aliases
type ErrorMessage = String
// Utility functions
def attempt[R](f: => R): R Or Throwable
// Version information
val ScalacticVersion: String
// Deprecated aliases (use new names instead)
@deprecated("Use NonEmptyList instead")
type Chain[+T] = NonEmptyList[T]
@deprecated("Use NonEmptyList instead")
val Chain = NonEmptyList
@deprecated("Use anyvals.End instead")
val End = anyvals.End
}Scalactic integrates seamlessly with ScalaTest for enhanced testing capabilities:
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalactic._
class ScalacticIntegrationSpec extends AnyFlatSpec with Matchers {
"Or type" should "work with ScalaTest matchers" in {
val result: Int Or String = Good(42)
result shouldBe a[Good[_]]
result.isGood should be(true)
result.get should be(42)
}
"Custom equality" should "be usable in tests" in {
implicit val caseInsensitive = new Equality[String] {
def areEqual(a: String, b: Any): Boolean =
b match {
case s: String => a.toLowerCase == s.toLowerCase
case _ => false
}
}
"Hello" should equal("HELLO")(decided by caseInsensitive)
}
"Validated types" should "ensure constraints" in {
val posInt = PosInt.from(5)
posInt shouldBe a[Good[_]]
posInt.get.value should be(5)
val invalidPosInt = PosInt.from(-1)
invalidPosInt shouldBe a[Bad[_]]
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-scalatest--scalatest-2-11