or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

async.mdfixtures.mdindex.mdmatchers.mdproperty.mdscalactic.mdtest-styles.md
tile.json

async.mddocs/

Asynchronous Testing

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.

Capabilities

AsyncTestSuite

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 AsyncTestRegistration

Usage 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
    }
  }
}

FutureOutcome

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)
}

Recovery Methods

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")
    }
  }
}

Complete Lastly

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)
    }
  }
}

Async Fixtures

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")
    }
  }
}

Types

/**
 * 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]
}