ScalaTest is a comprehensive testing framework for Scala and Java programmers with multiple testing styles and powerful matcher support.
ScalaTest provides comprehensive support for testing asynchronous code with Futures, custom execution contexts, and async test suites. This enables testing of non-blocking operations while maintaining proper test isolation and timing control.
Base trait for test suites that work with asynchronous operations returning Futures.
/**
* Base trait for asynchronous test suites
*/
trait AsyncTestSuite extends Suite with RecoverMethods with CompleteLastly {
/**
* Implicit execution context for running async operations
*/
implicit def executionContext: ExecutionContext
/**
* Transform test function to handle async operations
*/
def transformToFuture(testFun: => Future[compatible.Assertion]): FutureOutcome
/**
* Run a single asynchronous test
*/
def runAsyncTest(
testName: String,
args: Args,
includeIcon: Boolean,
invokeWithAsyncTestDataFunction: AsyncTestDataInvoker
): FutureOutcome
}
/**
* Async version of FunSuite
*/
abstract class AsyncFunSuite extends AsyncTestSuite with AsyncTestRegistration {
/**
* Register an asynchronous test
*/
protected def test(testName: String)(testFun: => Future[compatible.Assertion]): Unit
}
/**
* Async version of FlatSpec
*/
abstract class AsyncFlatSpec extends AsyncTestSuite with AsyncTestRegistration {
protected final class AsyncFlatSpecStringWrapper(string: String) {
def should(testFun: => Future[compatible.Assertion]): Unit
def must(testFun: => Future[compatible.Assertion]): Unit
def can(testFun: => Future[compatible.Assertion]): Unit
}
protected implicit def convertToAsyncFlatSpecStringWrapper(o: String): AsyncFlatSpecStringWrapper
}
/**
* Async versions of other test styles
*/
abstract class AsyncFunSpec extends AsyncTestSuite with AsyncTestRegistration
abstract class AsyncWordSpec extends AsyncTestSuite with AsyncTestRegistration
abstract class AsyncFreeSpec extends AsyncTestSuite with AsyncTestRegistration
abstract class AsyncFeatureSpec extends AsyncTestSuite with AsyncTestRegistrationUsage Examples:
import org.scalatest.funsuite.AsyncFunSuite
import scala.concurrent.Future
class AsyncExampleSuite extends AsyncFunSuite {
test("async computation should complete successfully") {
val futureResult = Future {
Thread.sleep(100) // Simulate async work
42
}
futureResult.map { result =>
assert(result == 42)
}
}
test("async operation with assertions") {
def asyncOperation(): Future[String] = Future.successful("Hello, World!")
asyncOperation().map { result =>
result should include ("World")
result should have length 13
}
}
}Wrapper for Future[Outcome] that provides transformation and composition methods.
/**
* Wrapper for Future[Outcome] with convenient transformation methods
*/
class FutureOutcome(private[scalatest] val underlying: Future[Outcome]) {
/**
* Transform the outcome using a function
*/
def map(f: Outcome => Outcome)(implicit executionContext: ExecutionContext): FutureOutcome
/**
* FlatMap operation for chaining FutureOutcomes
*/
def flatMap(f: Outcome => FutureOutcome)(implicit executionContext: ExecutionContext): FutureOutcome
/**
* Transform the Future[Outcome] directly
*/
def transform(f: Try[Outcome] => Try[Outcome])(implicit executionContext: ExecutionContext): FutureOutcome
/**
* Handle failures and recover with a different outcome
*/
def recover(pf: PartialFunction[Throwable, Outcome])(implicit executionContext: ExecutionContext): FutureOutcome
/**
* Convert to a Future[Outcome]
*/
def toFuture: Future[Outcome]
/**
* Block and wait for completion (mainly for testing)
*/
def isCompleted: Boolean
}
object FutureOutcome {
/**
* Create from a Future[Outcome]
*/
def apply(future: Future[Outcome]): FutureOutcome
/**
* Create from a successful outcome
*/
def successful(outcome: Outcome): FutureOutcome
/**
* Create from a failed outcome
*/
def failed(exception: Throwable): FutureOutcome
/**
* Create from a canceled outcome
*/
def canceled(exception: Throwable): FutureOutcome
}Usage Examples:
import org.scalatest._
import scala.concurrent.Future
import scala.util.{Success, Failure}
// Creating FutureOutcome instances
val successfulOutcome = FutureOutcome.successful(Succeeded)
val failedOutcome = FutureOutcome.failed(new RuntimeException("Test failed"))
// Transforming outcomes
val transformedOutcome = successfulOutcome.map {
case Succeeded => Succeeded
case failed => failed
}
// Chaining operations
val chainedOutcome = successfulOutcome.flatMap { outcome =>
if (outcome == Succeeded) {
FutureOutcome.successful(Succeeded)
} else {
FutureOutcome.failed(new RuntimeException("Chain failed"))
}
}
// Error recovery
val recoveredOutcome = failedOutcome.recover {
case ex: RuntimeException => Failed(ex)
case other => Failed(other)
}Utilities for handling exceptions in asynchronous tests.
trait RecoverMethods {
/**
* Recover from specific exception types in async operations
*/
def recoverToSucceededIf[T <: AnyRef](future: Future[Any])(implicit classTag: ClassTag[T]): Future[Assertion]
/**
* Recover expecting a specific exception to be thrown
*/
def recoverToExceptionIf[T <: AnyRef](future: Future[Any])(implicit classTag: ClassTag[T]): Future[T]
}Usage Examples:
import org.scalatest.RecoverMethods
import scala.concurrent.Future
class AsyncRecoveryExample extends AsyncFunSuite with RecoverMethods {
test("should recover from expected exception") {
val failingFuture = Future {
throw new IllegalArgumentException("Expected error")
}
// Test succeeds if the expected exception is thrown
recoverToSucceededIf[IllegalArgumentException] {
failingFuture
}
}
test("should capture expected exception") {
val failingFuture = Future {
throw new RuntimeException("Test error")
}
// Capture the exception for further assertions
recoverToExceptionIf[RuntimeException] {
failingFuture
}.map { exception =>
exception.getMessage should include ("Test error")
}
}
}Trait for cleanup operations that run after async tests complete.
trait CompleteLastly {
/**
* Register cleanup code to run after test completion
*/
def completeLastly(completeLastlyCode: => Unit): Unit
/**
* Register async cleanup code to run after test completion
*/
def completeLastly(completeLastlyCode: => Future[Unit]): Unit
}Usage Examples:
class AsyncResourceExample extends AsyncFunSuite with CompleteLastly {
test("should cleanup resources after async test") {
val resource = acquireResource()
// Register cleanup that runs regardless of test outcome
completeLastly {
resource.close()
println("Resource cleaned up")
}
// Test code that uses the resource
Future {
resource.process()
assert(resource.isProcessed)
}
}
test("should handle async cleanup") {
val asyncResource = acquireAsyncResource()
// Register async cleanup
completeLastly {
asyncResource.cleanupAsync()
}
asyncResource.processAsync().map { result =>
assert(result.isSuccess)
}
}
}Support for asynchronous setup and teardown in test fixtures.
/**
* Async version of fixture test suites
*/
trait AsyncFixtureTestSuite extends AsyncTestSuite {
type FixtureParam
/**
* Async fixture method that provides test data
*/
def withAsyncFixture(test: OneArgAsyncTest): FutureOutcome
}
/**
* One argument async test function
*/
abstract class OneArgAsyncTest extends (FixtureParam => Future[Outcome]) with TestData {
def apply(fixture: FixtureParam): Future[Outcome]
}Usage Examples:
import scala.concurrent.Future
class AsyncFixtureExample extends AsyncFunSuite {
case class DatabaseConnection(url: String) {
def query(sql: String): Future[List[String]] = Future.successful(List("result1", "result2"))
def close(): Future[Unit] = Future.successful(())
}
type FixtureParam = DatabaseConnection
def withAsyncFixture(test: OneArgAsyncTest): FutureOutcome = {
val connection = DatabaseConnection("jdbc:test://localhost")
complete {
withFixture(test.toNoArgAsyncTest(connection))
} lastly {
// Async cleanup
connection.close()
}
}
test("should query database asynchronously") { connection =>
connection.query("SELECT * FROM users").map { results =>
results should have size 2
results should contain ("result1")
}
}
}/**
* Test outcome for async operations
*/
sealed abstract class Outcome extends Product with Serializable
case object Succeeded extends Outcome
final case class Failed(exception: Throwable) extends Outcome
final case class Canceled(exception: Throwable) extends Outcome
case object Pending extends Outcome
/**
* Async test registration
*/
trait AsyncTestRegistration {
def registerAsyncTest(testName: String, testTags: Tag*)(testFun: => Future[compatible.Assertion]): Unit
def registerIgnoredAsyncTest(testName: String, testTags: Tag*)(testFun: => Future[compatible.Assertion]): Unit
}
/**
* Test data for async tests
*/
trait AsyncTestDataInvoker {
def apply(testData: TestData): Future[Outcome]
}Install with Tessl CLI
npx tessl i tessl/maven-org-scalatest--scalatest-2-13