ScalaTest User Guide

Getting started

Selecting testing styles

Defining base classes

Writing your first test

Using assertions

Tagging your tests

Running your tests

Sharing fixtures

Sharing tests

Using matchers

Testing with mock objects

Property-based testing

Asynchronous testing

Using Scala-js

Using Inside

Using OptionValues

Using EitherValues

Using PartialFunctionValues

Using PrivateMethodTester

Using WrapWith

Philosophy and design

Migrating to 3.0

Asynchronous testing

ScalaTest supports asynchronous non-blocking testing. Given a Future returned by the code you are testing, you need not block until the Future completes before performing assertions against its value. You can instead map those assertions onto the Future and return the resulting Future[Assertion] to ScalaTest. The test will complete asynchronously, when the Future[Assertion] completes.

The followings are the asynchronous style traits supported:

  • AsyncFeatureSpec
  • AsyncFlatSpec
  • AsyncFreeSpec
  • AsyncFunSpec
  • AsyncFunSuite
  • AsyncWordSpec

The asynchronous style traits follow the style of their synchronous cousins. For example, here's an AsyncFlatSpec:

package org.scalatest.examples.asyncflatspec

import org.scalatest.AsyncFlatSpec
import scala.concurrent.Future

class AddSpec extends AsyncFlatSpec {

  def addSoon(addends: Int*): Future[Int] = Future { addends.sum }

  behavior of "addSoon"

  it should "eventually compute a sum of passed Ints" in {
    val futureSum: Future[Int] = addSoon(1, 2)
    // You can map assertions onto a Future, then return
    // the resulting Future[Assertion] to ScalaTest:
    futureSum map { sum => assert(sum == 3) }
  }

  def addNow(addends: Int*): Int = addends.sum

  "addNow" should "immediately compute a sum of passed Ints" in {
    val sum: Int = addNow(1, 2)
    // You can also write synchronous tests. The body
    // must have result type Assertion:
    assert(sum == 3)
  }
}
    

Running the above AddSpec in the Scala interpreter would yield:

addSoon
  - should eventually compute a sum of passed Ints
  - should immediately compute a sum of passed Ints

Starting with version 3.0.0, ScalaTest assertions and matchers have result type Assertion. The result type of the first test in the example above, therefore, is Future[Assertion]. For clarity, here's the relevant code in a REPL session:

scala> import org.scalatest._
import org.scalatest._

scala> import Assertions._
import Assertions._

scala> import scala.concurrent.Future
import scala.concurrent.Future

scala> import scala.concurrent.ExecutionContext
import scala.concurrent.ExecutionContext

scala> implicit val executionContext = ExecutionContext.Implicits.global
executionContext: scala.concurrent.ExecutionContextExecutor = scala.concurrent.impl.ExecutionContextImpl@26141c5b

scala> def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
addSoon: (addends: Int*)scala.concurrent.Future[Int]

scala> val futureSum: Future[Int] = addSoon(1, 2)
futureSum: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@721f47b2

scala> futureSum map { sum => assert(sum == 3) }
res0: scala.concurrent.Future[org.scalatest.Assertion] = scala.concurrent.impl.Promise$DefaultPromise@3955cfcb

The second test has result type Assertion:

scala> def addNow(addends: Int*): Int = addends.sum
addNow: (addends: Int*)Int

scala> val sum: Int = addNow(1, 2)
sum: Int = 3

scala> assert(sum == 3)
res1: org.scalatest.Assertion = Succeeded

When AddSpec is constructed, the second test will be implicitly converted to Future[Assertion] and registered. The implicit conversion is from Assertion to Future[Assertion], so you must end synchronous tests in some ScalaTest assertion or matcher expression. If a test would not otherwise end in type Assertion, you can place succeed at the end of the test. succeed, a field in trait Assertions, returns the Succeeded singleton:

scala> succeed
res2: org.scalatest.Assertion = Succeeded

Thus placing succeed at the end of a test body will satisfy the type checker:

"addNow" should "immediately compute a sum of passed Ints" in {
  val sum: Int = addNow(1, 2)
  assert(sum == 3)
  println("hi") // println has result type Unit
  succeed       // succeed has result type Assertion
}
    

Asynchronous execution model

Asynchronous style traits extend AsyncTestSuite, which provides an implicit scala.concurrent.ExecutionContext named executionContext. This execution context is used by asynchronous style traits to transform the Future[Assertion]s returned by each test into the FutureOutcome returned by the test function passed to withFixture. This ExecutionContext is also intended to be used in the tests, including when you map assertions onto futures.

On both the JVM and Scala.js, the default execution context provided by ScalaTest's asynchronous testing styles confines execution to a single thread per test. On JavaScript, where single-threaded execution is the only possibility, the default execution context is scala.scalajs.concurrent.JSExecutionContext.Implicits.queue. On the JVM, the default execution context is a serial execution context provided by ScalaTest itself.

When ScalaTest's serial execution context is called upon to execute a task, that task is recorded in a queue for later execution. For example, one task that will be placed in this queue is the task that transforms the Future[Assertion] returned by an asynchronous test body to the FutureOutcome returned from the test function. Other tasks that will be queued are any transformations of, or callbacks registered on, Futures that occur in your test body, including any assertions you map onto Futures. Once the test body returns, the thread that executed the test body will execute the tasks in that queue one after another, in the order they were enqueued.

ScalaTest provides its serial execution context as the default on the JVM for three reasons. First, most often running both tests and suites in parallel does not give a significant performance boost compared to just running suites in parallel. Thus parallel execution of Future transformations within individual tests is not generally needed for performance reasons.

Second, if multiple threads are operating in the same suite concurrently, you'll need to make sure access to any mutable fixture objects by multiple threads is synchronized. Although access to mutable state along the same linear chain of Future transformations need not be synchronized, this does not hold true for callbacks, and in general it is easy to make a mistake. Simply put: synchronizing access to shared mutable state is difficult and error prone. Because ScalaTest's default execution context on the JVM confines execution of Future transformations and call backs to a single thread, you need not (by default) worry about synchronizing access to mutable state in your asynchronous-style tests.

Third, asynchronous-style tests need not be complete when the test body returns, because the test body returns a Future[Assertion]. This Future[Assertion] will often represent a test that has not yet completed. As a result, when using a more traditional execution context backed by a thread-pool, you could potentially start many more tests executing concurrently than there are threads in the thread pool. The more concurrently execute tests you have competing for threads from the same limited thread pool, the more likely it will be that tests will intermitently fail due to timeouts.

Using ScalaTest's serial execution context on the JVM will ensure the same thread that produced the Future[Assertion] returned from a test body is also used to execute any tasks given to the execution context while executing the test body—and that thread will not be allowed to do anything else until the test completes. If the serial execution context's task queue ever becomes empty while the Future[Assertion] returned by that test's body has not yet completed, the thread will block until another task for that test is enqueued. Although it may seem counter-intuitive, this blocking behavior means the total number of tests allowed to run concurrently will be limited to the total number of threads executing suites. This fact means you can tune the thread pool such that maximum performance is reached while avoiding (or at least, reducing the likelihood of) tests that fail due to timeouts because of thread competition.

This thread confinement strategy does mean, however, that when you are using the default execution context on the JVM, you must be sure to never block in the test body waiting for a task to be completed by the execution context. If you block, your test will never complete. This kind of problem will be obvious, because the test will consistently hang every time you run it. (If a test is hanging, and you're not sure which one it is, enable slowpoke notifications.) If you really do want to block in your tests, you may wish to just use a traditional style traits with ScalaFutures instead. Alternatively, you could override the executionContext and use a traditional ExecutionContext backed by a thread pool. This will enable you to block in an asynchronous-style test on the JVM, but you'll need to worry about synchronizing access to shared mutable state.

To use a different execution context, just override executionContext. For example, if you prefer to use the runNow execution context on Scala.js instead of the default queue, you would write:

// on Scala.js
implicit override def executionContext = scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow

If you prefer on the JVM to use the global execution context, which is backed by a thread pool, instead of ScalaTest's default serial execution contex, which confines execution to a single thread, you would write:

// on the JVM (and also compiles on Scala.js, giving
// you the queue execution context)
implicit override def executionContext = scala.concurrent.ExecutionContext.Implicits.global

Serial and parallel test execution

By default (unless you mix in ParallelTestExecution), tests in an asynchronous style traits will be executed one after another, i.e., serially. This is true whether those tests return Assertion or Future[Assertion], no matter what threads are involved. This default behavior allows you to re-use a shared fixture, such as an external database that needs to be cleaned after each test, in multiple tests in async-style suites. This is implemented by registering each test, other than the first test, to run as a continuation after the previous test completes.

If you want the tests of an asynchronous style trait to be executed in parallel, you must mix in ParallelTestExecution and enable parallel execution of tests in your build. You enable parallel execution in Runner with the -P command line argument. In the ScalaTest Maven Plugin, set parallel to true. In sbt, parallel execution is the default, but to be explicit you can write:

parallelExecution in Test := true // the default in sbt

On the JVM, if both ParallelTestExecution is mixed in and parallel execution is enabled in the build, tests in an async-style suite will be started in parallel, using threads from the Distributor, and allowed to complete in parallel, using threads from the executionContext. If you are using ScalaTest's serial execution context, the JVM default, asynchronous tests will run in parallel very much like traditional style trait's tests run in parallel: 1) Because ParallelTestExecution extends OneInstancePerTest, each test will run in its own instance of the test class, you need not worry about synchronizing access to mutable instance state shared by different tests in the same suite. 2) Because the serial execution context will confine the execution of each test to the single thread that executes the test body, you need not worry about synchronizing access to shared mutable state accessed by transformations and callbacks of Futures inside the test.

If ParallelTestExecution is mixed in but parallel execution of suites is not enabled, asynchronous tests on the JVM will be started sequentially, by the single thread that invoked run, but without waiting for one test to complete before the next test is started. As a result, asynchronous tests will be allowed to complete in parallel, using threads from the executionContext. If you are using the serial execution context, however, you'll see the same behavior you see when parallel execution is disabled and a traditional suite that mixes in ParallelTestExecution is executed: the tests will run sequentially. If you use an execution context backed by a thread-pool, such as global, however, even though tests will be started sequentially by one thread, they will be allowed to run concurrently using threads from the execution context's thread pool.

The latter behavior is essentially what you'll see on Scala.js when you execute a suite that mixes in ParallelTestExecution. Because only one thread exists when running under JavaScript, you can't "enable parallel execution of suites." However, it may still be useful to run tests in parallel on Scala.js, because tests can invoke API calls that are truly asynchronous by calling into external APIs that take advantage of non-JavaScript threads. Thus on Scala.js, ParallelTestExecution allows asynchronous tests to run in parallel, even though they must be started sequentially. This may give you better performance when you are using API calls in your Scala.js tests that are truly asynchronous.

Futures and expected exceptions

If you need to test for expected exceptions in the context of futures, you can use the recoverToSucceededIf and recoverToExceptionIf methods of trait RecoverMethods. Because this trait is mixed into supertrait AsyncTestSuite, both of these methods are available by default in an asynchronous style traits.

If you just want to ensure that a future fails with a particular exception type, and do not need to inspect the exception further, use recoverToSucceededIf:

recoverToSucceededIf[IllegalStateException] { // Result type: Future[Assertion]
  emptyStackActor ? Peek
}

The recoverToSucceededIf method performs a job similar to assertThrows, except in the context of a future. It transforms a Future of any type into a Future[Assertion] that succeeds only if the original future fails with the specified exception. Here's an example in the REPL:

scala> import org.scalatest.RecoverMethods._
import org.scalatest.RecoverMethods._

scala> import scala.concurrent.Future
import scala.concurrent.Future

scala> import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext.Implicits.global

scala> recoverToSucceededIf[IllegalStateException] {
|   Future { throw new IllegalStateException }
| }
res0: scala.concurrent.Future[org.scalatest.Assertion] = ...

scala> res0.value
res1: Option[scala.util.Try[org.scalatest.Assertion]] = Some(Success(Succeeded))

Otherwise it fails with an error message similar to those given by assertThrows:

scala> recoverToSucceededIf[IllegalStateException] {
|   Future { throw new RuntimeException }
| }
res2: scala.concurrent.Future[org.scalatest.Assertion] = ...

scala> res2.value
res3: Option[scala.util.Try[org.scalatest.Assertion]] =
Some(Failure(org.scalatest.exceptions.TestFailedException: Expected exception
java.lang.IllegalStateException to be thrown, but java.lang.RuntimeException
was thrown))

scala> recoverToSucceededIf[IllegalStateException] {
|   Future { 42 }
| }
res4: scala.concurrent.Future[org.scalatest.Assertion] = ...

scala> res4.value
res5: Option[scala.util.Try[org.scalatest.Assertion]] =
Some(Failure(org.scalatest.exceptions.TestFailedException: Expected exception
java.lang.IllegalStateException to be thrown, but no exception was thrown))

The recoverToExceptionIf method differs from the recoverToSucceededIf in its behavior when the assertion succeeds: recoverToSucceededIf yields a Future[Assertion], whereas recoverToExceptionIf yields a Future[T], where T is the expected exception type.

recoverToExceptionIf[IllegalStateException] { // Result type: Future[IllegalStateException]
  emptyStackActor ? Peek
}

In other words, recoverToExceptionIf is to intercept as recovertToSucceededIf is to assertThrows. The first one allows you to perform further assertions on the expected exception. The second one gives you a result type that will satisfy the type checker at the end of the test body. Here's an example showing recoverToExceptionIf in the REPL:

scala> val futureEx =
|   recoverToExceptionIf[IllegalStateException] {
|     Future { throw new IllegalStateException("hello") }
|   }
futureEx: scala.concurrent.Future[IllegalStateException] = ...

scala> futureEx.value
res6: Option[scala.util.Try[IllegalStateException]] =
Some(Success(java.lang.IllegalStateException: hello))

scala> futureEx map { ex => assert(ex.getMessage == "world") }
res7: scala.concurrent.Future[org.scalatest.Assertion] = ...

scala> res7.value
res8: Option[scala.util.Try[org.scalatest.Assertion]] =
Some(Failure(org.scalatest.exceptions.TestFailedException: "[hello]" did not equal "[world]"))

Using asynchronous withFixture

You'll use NoArgAsyncTest and OneArgAsyncTest for asynchronous style traits, the following is the default implementation for withFixture:

// Default implementation in trait AsyncTestSuite
protected def withFixture(test: NoArgAsyncTest): FutureOutcome = {
  test()
}

You can, therefore, override withFixture to perform setup before invoking the test function, and/or perform cleanup after the test completes. The recommended way to ensure cleanup is performed after a test completes is to use the complete-lastly syntax, defined in supertrait CompleteLastly. The complete-lastly syntax will ensure that cleanup will occur whether future-producing code completes abruptly by throwing an exception, or returns normally yielding a future. In the latter case, complete-lastly will register the cleanup code to execute asynchronously when the future completes.

The withFixture method is designed to be stacked, and to enable this, you should always call the super implementation of withFixture, and let it invoke the test function rather than invoking the test function directly. In other words, instead of writing “test()”, you should write “super.withFixture(test)”, like this:

// Your implementation
override def withFixture(test: NoArgAsyncTest) = {

  // Perform setup here

  complete {
    super.withFixture(test) // Invoke the test function
  } lastly {
    // Perform cleanup here
  }
}

If you have no cleanup to perform, you can write withFixture like this instead:

// Your implementation
override def withFixture(test: NoArgAsyncTest) = {

  // Perform setup here

  super.withFixture(test) // Invoke the test function
}

If you want to perform an action only for certain outcomes, you'll need to register code performing that action as a callback on the Future using one of Future's registration methods: onComplete, onSuccess, or onFailure. Note that if a test fails, that will be treated as a scala.util.Success (org.scalatest.Failed). So if you want to perform an action if a test fails, for example, you'd register the callback using onSuccess.

Here's an example in which withFixture(NoArgAsyncTest) is used to take a snapshot of the working directory if a test fails, and send that information to the standard output stream:

package org.scalatest.examples.asyncflatspec.noargasynctest

import java.io.File
import org.scalatest._
import scala.concurrent.Future

class ExampleSpec extends AsyncFlatSpec {

  override def withFixture(test: NoArgAsyncTest) = {

    super.withFixture(test) onFailedThen { _ =>
      val currDir = new File(".")
      val fileNames = currDir.list()
      info("Dir snapshot: " + fileNames.mkString(", "))
    }
  }

  def addSoon(addends: Int*): Future[Int] = Future { addends.sum }

  "This test" should "succeed" in {
    addSoon(1, 1) map { sum => assert(sum == 2) }
  }

  it should "fail" in {
    addSoon(1, 1) map { sum => assert(sum == 3) }
  }
}

Running this version of ExampleSpec in the interpreter in a directory with two files, hello.txt and world.txt would give the following output:

scala> org.scalatest.run(new ExampleSpec)
ExampleSpec:
This test
- should succeed
- should fail *** FAILED ***
2 did not equal 3 (:33)

Note that the NoArgAsyncTest passed to withFixture, in addition to an apply method that executes the test, also includes the test name and the config map passed to runTest. Thus you can also use the test name and configuration objects in your withFixture implementation.

Lastly, if you want to transform the outcome in some way in withFixture, you'll need to use either the map or transform methods of Future, like this:

// Your implementation
override def withFixture(test: NoArgAsyncTest) = {

  // Perform setup here

  val futureOutcome = super.withFixture(test) // Invoke the test function

  futureOutcome change { outcome =>
    // transform the outcome into a new outcome here
  }
}

Note that a NoArgAsyncTest's apply method will return a scala.util.Failure only if the test completes abruptly with a "test-fatal" exception (such as OutOfMemoryError) that should cause the suite to abort rather than the test to fail. Thus usually you would use map to transform future outcomes, not transform, so that such test-fatal exceptions pass through unchanged. The suite will abort asynchronously with any exception returned from NoArgAsyncTest's apply method in a scala.util.Failure.

If all or most tests need the same fixture, you can avoid some of the boilerplate of the loan-fixture method approach by using a fixture.AsyncTestSuite and overriding withFixture(OneArgAsyncTest). Each test in a fixture.AsyncTestSuite takes a fixture as a parameter, allowing you to pass the fixture into the test. You must indicate the type of the fixture parameter by specifying FixtureParam, and implement a withFixture method that takes a OneArgAsyncTest. This withFixture method is responsible for invoking the one-arg async test function, so you can perform fixture set up before invoking and passing the fixture into the test function, and ensure clean up is performed after the test completes.

To enable the stacking of traits that define withFixture(NoArgAsyncTest), it is a good idea to let withFixture(NoArgAsyncTest) invoke the test function instead of invoking the test function directly. To do so, you'll need to convert the OneArgAsyncTest to a NoArgAsyncTest. You can do that by passing the fixture object to the toNoArgAsyncTest method of OneArgAsyncTest. In other words, instead of writing “test(theFixture)”, you'd delegate responsibility for invoking the test function to the withFixture(NoArgAsyncTest) method of the same instance by writing:

withFixture(test.toNoArgAsyncTest(theFixture))

Here's a complete example:

package org.scalatest.examples.asyncflatspec.oneargasynctest

import org.scalatest._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext

// Defining actor messages
sealed abstract class StringOp
case object Clear extends StringOp
case class Append(value: String) extends StringOp
case object GetValue

class StringActor { // Simulating an actor
  private final val sb = new StringBuilder
  def !(op: StringOp): Unit =
    synchronized {
      op match {
        case Append(value) => sb.append(value)
        case Clear => sb.clear()
      }
    }
  def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[String] =
    Future {
      synchronized { sb.toString }
    }
}

class ExampleSpec extends fixture.AsyncFlatSpec {

  type FixtureParam = StringActor

  def withFixture(test: OneArgAsyncTest): FutureOutcome = {

    val actor = new StringActor
    complete {
      actor ! Append("ScalaTest is ") // set up the fixture
      withFixture(test.toNoArgAsyncTest(actor))
    } lastly {
      actor ! Clear // ensure the fixture will be cleaned up
    }
  }

  "Testing" should "be easy" in { actor =>
    actor ! Append("easy!")
    val futureString = actor ? GetValue
    futureString map { s =>
      assert(s == "ScalaTest is easy!")
    }
  }

  it should "be fun" in { actor =>
    actor ! Append("fun!")
    val futureString = actor ? GetValue
    futureString map { s =>
      assert(s == "ScalaTest is fun!")
    }
  }
}

Next, learn about using ScalaTest with Scala-js.

ScalaTest is brought to you by Bill Venners and Artima.
ScalaTest is free, open-source software released under the Apache 2.0 license.

If your company loves ScalaTest, please consider sponsoring the project.

Copyright © 2009-2024 Artima, Inc. All Rights Reserved.

artima