package scalatest
ScalaTest's main traits, classes, and other members, including members supporting ScalaTest's DSL for the Scala interpreter.
- Source
- package.scala
- Alphabetic
- By Inheritance
- scalatest
- AnyRef
- Any
- Hide All
- Show All
- Public
- Protected
Package Members
- package compatible
- package concurrent
ScalaTest's main traits, classes, and other members, including members supporting ScalaTest's DSL for the Scala interpreter.
- package easymock
- package enablers
- package events
- package exceptions
- package fixture
- package jmock
- package junit
- package matchers
- package mockito
- package path
- package prop
ScalaTest's main traits, classes, and other members, including members supporting ScalaTest's DSL for the Scala interpreter.
- package refspec
- package selenium
- package tagobjects
- package tags
- package testng
- package time
- package tools
- package words
Type Members
- trait Alerter extends AnyRef
Trait providing an
apply
method to which alert messages about a running suite of tests can be reported.Trait providing an
apply
method to which alert messages about a running suite of tests can be reported.An
Alerter
is essentially used to wrap aReporter
and provide easy ways to send alert messages to thatReporter
via anAlertProvided
event.Alerter
contains anapply
method that takes a string and an optional payload object of typeAny
. TheAlerter
will forward the passed alertmessage
string to theReporter
as themessage
parameter, and the optional payload object as thepayload
parameter, of anAlertProvided
event.For insight into the differences between
Alerter
,Notifier
, andInformer
, see the main documentation for traitAlerting
. - trait Alerting extends AnyRef
Trait that contains the
alert
method, which can be used to send an alert to the reporter.Trait that contains the
alert
method, which can be used to send an alert to the reporter.One difference between
alert
and theinfo
method ofInformer
is thatinfo
messages provided during a test are recorded and sent as part of test completion event, whereasalert
messages are sent right away asAlertProvided
messages. For long-running tests,alert
allows you to send "alert notifications" to the reporter right away, so users can be made aware of potential problems being experienced by long-running tests. By contrast,info
messages will only be seen by the user after the test has completed, and are more geared towards specification (such as Given/When/Then messages) than notification.The difference between
alert
and theupdate
method ofUpdating
is thatalert
is intended to be used for warnings or notifications of potential problems, whereasupdate
is just for status updates. In string reporters for which ANSI color is enabled,update
notifications are shown in green andalert
notifications in yellow. - trait AppendedClues extends AnyRef
Trait providing an implicit conversion that allows clues to be placed after a block of code.
Trait providing an implicit conversion that allows clues to be placed after a block of code.
You can use the
withClue
construct provided byAssertions
, which is extended by every style trait in ScalaTest, to add extra information to reports of failed or canceled tests. ThewithClue
fromAssertions
places the "clue string" at the front, both in the code and in the resulting message:withClue("This is a prepended clue;") { 1 + 1 should equal (3) }
The above expression will yield the failure message:
This is a prepended clue; 2 did not equal 3
If you mix in this trait, or import its members via its companion object, you can alternatively place the clue string at the end, like this:
{ 1 + 1 should equal (3) } withClue "now the clue comes after"
The above expression will yield the failure message:
2 did not equal 3 now the clue comes after
If no space is already present, either at the beginning of the clue string or at the end of the current message, a space will be placed between the two, unless the clue string starts with one of the punctuation characters: comma (
,
), period (.
), or semicolon (;
). For example, the failure message in the above example includes an extra space inserted between 3 and now.By contrast this code, which has a clue string starting with comma:
{ 1 + 1 should equal (3) } withClue ", now the clue comes after"
Will yield a failure message with no extra inserted space:
2 did not equal 3, now the clue comes after
The
withClue
method will only append the clue string to the detail message of exception types that mix in theModifiableMessage
trait. See the documentation forModifiableMessage
for more information.Note: the reason this functionality is not provided by
Assertions
directly, like the prependedwithClue
construct, is because appended clues require an implicit conversion. ScalaTest only gives you one implicit conversion by default in any test class to minimize the potential for conflicts with other implicit conversions you may be using. All other implicit conversions, including the one provided by this trait, you must explicitly invite into your code through inheritance or an import. - case class Args(reporter: Reporter, stopper: Stopper = Stopper.default, filter: Filter = Filter.default, configMap: ConfigMap = ConfigMap.empty, distributor: Option[Distributor] = None, tracker: Tracker = Tracker.default, chosenStyles: Set[String] = Set.empty, runTestInNewInstance: Boolean = false, distributedTestSorter: Option[DistributedTestSorter] = None, distributedSuiteSorter: Option[DistributedSuiteSorter] = None) extends Product with Serializable
Arguments bundle passed to four of ScalaTest's lifecycle methods:
run
,runNestedSuites
,runTests
, andrunTest
.Arguments bundle passed to four of ScalaTest's lifecycle methods:
run
,runNestedSuites
,runTests
, andrunTest
.The signatures of these methods, defined in trait
Suite
, are:def run(testName: Option[String], args: Args) def runNestedSuites(args: Args) def runTests(testName: Option[String], args: Args) def runTest(testName: String, args: Args)
The purpose of bundling these arguments into an
Args
object instead of passing them in individually is to make the signature of these four lifecycle methods easier to read, write, and remember, as well as to make the methods more pleasant to override in user code.- reporter
the
Reporter
to which results will be reported- stopper
the
Stopper
that will be consulted to determine whether to stop execution early.- filter
a
Filter
with which to filter tests based on their tags- configMap
a
ConfigMap
of key-value pairs that can be used by the executingSuite
of tests.- distributor
an optional
Distributor
, into which to put nestedSuite
s to be executed by another entity, such as concurrently by a pool of threads. IfNone
, nestedSuite
s will be executed sequentially.- tracker
a
Tracker
trackingOrdinal
s being fired by the current thread.- chosenStyles
a (possibly empty)
Set
ofString
s specifying the run's chosen styles- runTestInNewInstance
a flag used to pass information between run methods in
OneInstancePerTest
andParallelTestExecution
.- distributedTestSorter
an optional
DistributedTestSorter
used byParallelTestExecution
to sort the events for the parallel-executed tests of one suite back into sequential order on the fly, with a timeout in case a test takes too long to complete- distributedSuiteSorter
an optional
DistributedSuiteSorter
used byParallelTestExecution
to ensure the events for the parallel-executed suites are sorted back into sequential order, with a timeout in case a suite takes to long to complete, even when tests are executed in parallel
- Exceptions thrown
NullArgumentException
if any passed parameter isnull
.
- type Assertion = scalatest.compatible.Assertion
- trait Assertions extends TripleEquals
Trait that contains ScalaTest's basic assertion methods.
Trait that contains ScalaTest's basic assertion methods.
You can use the assertions provided by this trait in any ScalaTest
Suite
, becauseSuite
mixes in this trait. This trait is designed to be used independently of anything else in ScalaTest, though, so you can mix it into anything. (You can alternatively import the methods defined in this trait. For details, see the documentation for theAssertions
companion object.In any Scala program, you can write assertions by invoking
assert
and passing in aBoolean
expression, such as:val left = 2 val right = 1 assert(left == right)
If the passed expression is
true
,assert
will return normally. Iffalse
, Scala'sassert
will complete abruptly with anAssertionError
. This behavior is provided by theassert
method defined in objectPredef
, whose members are implicitly imported into every Scala source file. ThisAssertions
trait defines anotherassert
method that hides the one inPredef
. It behaves the same, except that iffalse
is passed it throwsTestFailedException
instead ofAssertionError
. Why? Because unlikeAssertionError
,TestFailedException
carries information about exactly which item in the stack trace represents the line of test code that failed, which can help users more quickly find an offending line of code in a failing test. In addition, ScalaTest'sassert
provides better error messages than Scala'sassert
.If you pass the previous
Boolean
expression,left == right
toassert
in a ScalaTest test, a failure will be reported that, becauseassert
is implemented as a macro, includes reporting the left and right values. For example, given the same code as above but using ScalaTest assertions:import org.scalatest.Assertions._ val left = 2 val right = 1 assert(left == right)
The detail message in the thrown
TestFailedException
from thisassert
will be: "2 did not equal 1".ScalaTest's
assert
macro works by recognizing patterns in the AST of the expression passed toassert
and, for a finite set of common expressions, giving an error message that an equivalent ScalaTest matcher expression would give. Here are some examples, wherea
is 1,b
is 2,c
is 3,d
is 4,xs
isList(a, b, c)
, andnum
is 1.0:assert(a == b || c >= d) // Error message: 1 did not equal 2, and 3 was not greater than or equal to 4
assert(xs.exists(_ == 4)) // Error message: List(1, 2, 3) did not contain 4
assert("hello".startsWith("h") && "goodbye".endsWith("y")) // Error message: "hello" started with "h", but "goodbye" did not end with "y"
assert(num.isInstanceOf[Int]) // Error message: 1.0 was not instance of scala.Int
assert(Some(2).isEmpty) // Error message: Some(2) was not emptyFor expressions that are not recognized, the macro currently prints out a string representation of the (desugared) AST and adds
"was false"
. Here are some examples of error messages for unrecognized expressions:assert(None.isDefined) // Error message: scala.None.isDefined was false
assert(xs.exists(i => i > 10)) // Error message: xs.exists(((i: Int) => i.>(10))) was falseYou can augment the standard error message by providing a
String
as a second argument toassert
, like this:val attempted = 2 assert(attempted == 1, "Execution was attempted " + left + " times instead of 1 time")
Using this form of
assert
, the failure report will be more specific to your problem domain, thereby helping you debug the problem. ThisAssertions
trait also mixes in theTripleEquals
, which gives you a===
operator that allows you to customizeEquality
, perform equality checks with numericTolerance
, and enforce type constraints at compile time with sibling traitsTypeCheckedTripleEquals
andConversionCheckedTripleEquals
.Expected results
Although the
assert
macro provides a natural, readable extension to Scala'sassert
mechanism that provides good error messages, as the operands become lengthy, the code becomes less readable. In addition, the error messages generated for==
and===
comparisons don't distinguish between actual and expected values. The operands are just calledleft
andright
, because if one were namedexpected
and the otheractual
, it would be difficult for people to remember which was which. To help with these limitations of assertions,Suite
includes a method calledassertResult
that can be used as an alternative toassert
. To useassertResult
, you place the expected value in parentheses afterassertResult
, followed by curly braces containing code that should result in the expected value. For example:val a = 5 val b = 2 assertResult(2) { a - b }
In this case, the expected value is
2
, and the code being tested isa - b
. This assertion will fail, and the detail message in theTestFailedException
will read, "Expected 2, but got 3."Forcing failures
If you just need the test to fail, you can write:
fail()
Or, if you want the test to fail with a message, write:
fail("I've got a bad feeling about this")
Achieving success
In async style tests, you must end your test body with either
Future[Assertion]
orAssertion
. ScalaTest's assertions (including matcher expressions) have result typeAssertion
, so ending with an assertion will satisfy the compiler. If a test body or function body passed toFuture.map
does not end with typeAssertion
, however, you can fix the type error by placingsucceed
at the end of the test or function body:succeed // Has type Assertion
Expected exceptions
Sometimes you need to test whether a method throws an expected exception under certain circumstances, such as when invalid arguments are passed to the method. You can do this in the JUnit 3 style, like this:
val s = "hi" try { s.charAt(-1) fail() } catch { case _: IndexOutOfBoundsException => // Expected, so continue }
If
charAt
throwsIndexOutOfBoundsException
as expected, control will transfer to the catch case, which does nothing. If, however,charAt
fails to throw an exception, the next statement,fail()
, will be run. Thefail
method always completes abruptly with aTestFailedException
, thereby signaling a failed test.To make this common use case easier to express and read, ScalaTest provides two methods:
assertThrows
andintercept
. Here's how you useassertThrows
:val s = "hi" assertThrows[IndexOutOfBoundsException] { // Result type: Assertion s.charAt(-1) }
This code behaves much like the previous example. If
charAt
throws an instance ofIndexOutOfBoundsException
,assertThrows
will returnSucceeded
. But ifcharAt
completes normally, or throws a different exception,assertThrows
will complete abruptly with aTestFailedException
.The
intercept
method behaves the same asassertThrows
, except that instead of returningSucceeded
,intercept
returns the caught exception so that you can inspect it further if you wish. For example, you may need to ensure that data contained inside the exception have expected values. Here's an example:val s = "hi" val caught = intercept[IndexOutOfBoundsException] { // Result type: IndexOutOfBoundsException s.charAt(-1) } assert(caught.getMessage.indexOf("-1") != -1)
Checking that a snippet of code does or does not compile
Often when creating libraries you may wish to ensure that certain arrangements of code that represent potential “user errors” do not compile, so that your library is more error resistant. ScalaTest's
Assertions
trait includes the following syntax for that purpose:assertDoesNotCompile("val a: String = 1")
If you want to ensure that a snippet of code does not compile because of a type error (as opposed to a syntax error), use:
assertTypeError("val a: String = 1")
Note that the
assertTypeError
call will only succeed if the given snippet of code does not compile because of a type error. A syntax error will still result on a thrownTestFailedException
.If you want to state that a snippet of code does compile, you can make that more obvious with:
assertCompiles("val a: Int = 1")
Although the previous three constructs are implemented with macros that determine at compile time whether the snippet of code represented by the string does or does not compile, errors are reported as test failures at runtime.
Assumptions
Trait
Assertions
also provides methods that allow you to cancel a test. You would cancel a test if a resource required by the test was unavailable. For example, if a test requires an external database to be online, and it isn't, the test could be canceled to indicate it was unable to run because of the missing database. Such a test assumes a database is available, and you can use theassume
method to indicate this at the beginning of the test, like this:assume(database.isAvailable)
For each overloaded
assert
method, traitAssertions
provides an overloadedassume
method with an identical signature and behavior, except theassume
methods throwTestCanceledException
whereas theassert
methods throwTestFailedException
. As withassert
,assume
hides a Scala method inPredef
that performs a similar function, but throwsAssertionError
. And just as you can withassert
, you will get an error message extracted by a macro from the AST passed toassume
, and can optionally provide a clue string to augment this error message. Here are some examples:assume(database.isAvailable, "The database was down again") assume(database.getAllUsers.count === 9)
Forcing cancelations
For each overloaded
fail
method, there's a correspondingcancel
method with an identical signature and behavior, except thecancel
methods throwTestCanceledException
whereas thefail
methods throwTestFailedException
. Thus if you just need to cancel a test, you can write:cancel()
If you want to cancel the test with a message, just place the message in the parentheses:
cancel("Can't run the test because no internet connection was found")
Getting a clue
If you want more information that is provided by default by the methods if this trait, you can supply a "clue" string in one of several ways. The extra information (or "clues") you provide will be included in the detail message of the thrown exception. Both
assert
andassertResult
provide a way for a clue to be included directly,intercept
does not. Here's an example of clues provided directly inassert
:assert(1 + 1 === 3, "this is a clue")
and in
assertResult
:assertResult(3, "this is a clue") { 1 + 1 }
The exceptions thrown by the previous two statements will include the clue string,
"this is a clue"
, in the exception's detail message. To get the same clue in the detail message of an exception thrown by a failedintercept
call requires usingwithClue
:withClue("this is a clue") { intercept[IndexOutOfBoundsException] { "hi".charAt(-1) } }
The
withClue
method will only prepend the clue string to the detail message of exception types that mix in theModifiableMessage
trait. See the documentation forModifiableMessage
for more information. If you wish to place a clue string after a block of code, see the documentation forAppendedClues
.Note: ScalaTest's
assertTypeError
construct is in part inspired by theillTyped
macro of shapeless. - abstract class AsyncFeatureSpec extends AsyncFeatureSpecLike
Enables testing of asynchronous code without blocking, using a style consistent with traditional
FeatureSpec
tests.Enables testing of asynchronous code without blocking, using a style consistent with traditional
FeatureSpec
tests.Recommended Usage: AsyncFeatureSpec
is intended to enable users ofFeatureSpec
to write non-blocking asynchronous tests that are consistent with their traditionalFeatureSpec
tests. Note:AsyncFeatureSpec
is intended for use in special situations where non-blocking asynchronous testing is needed, with classFeatureSpec
used for general needs.Given a
Future
returned by the code you are testing, you need not block until theFuture
completes before performing assertions against its value. You can instead map those assertions onto theFuture
and return the resultingFuture[Assertion]
to ScalaTest. The test will complete asynchronously, when theFuture[Assertion]
completes.Although not required,
FeatureSpec
is often used together withGivenWhenThen
to express acceptance requirements in more detail. Here's an exampleAsyncFeatureSpec
:package org.scalatest.examples.asyncfeaturespec
import org.scalatest._ import scala.concurrent.Future import scala.concurrent.ExecutionContext
// Defining actor messages case object IsOn case object PressPowerButton
class TVSetActor { // Simulating an actor private var on: Boolean = false def !(msg: PressPowerButton.type): Unit = synchronized { on = !on } def ?(msg: IsOn.type)(implicit c: ExecutionContext): Future[Boolean] = Future { synchronized { on } } }
class TVSetActorSpec extends AsyncFeatureSpec with GivenWhenThen {
implicit override def executionContext = scala.concurrent.ExecutionContext.Implicits.global
info("As a TV set owner") info("I want to be able to turn the TV on and off") info("So I can watch TV when I want") info("And save energy when I'm not watching TV")
feature("TV power button") { scenario("User presses power button when TV is off") {
Given("a TV set that is switched off") val tvSetActor = new TVSetActor
When("the power button is pressed") tvSetActor ! PressPowerButton
Then("the TV should switch on") val futureBoolean = tvSetActor ? IsOn futureBoolean map { isOn => assert(isOn) } }
scenario("User presses power button when TV is on") {
Given("a TV set that is switched on") val tvSetActor = new TVSetActor tvSetActor ! PressPowerButton
When("the power button is pressed") tvSetActor ! PressPowerButton
Then("the TV should switch off") val futureBoolean = tvSetActor ? IsOn futureBoolean map { isOn => assert(!isOn) } } } }Note: for more information on the calls to
Given
,When
, andThen
, see the documentation for traitGivenWhenThen
and theInformers
section below.An
AsyncFeatureSpec
contains feature clauses and scenarios. You define a feature clause withfeature
, and a scenario withscenario
. Bothfeature
andscenario
are methods, defined inAsyncFeatureSpec
, which will be invoked by the primary constructor ofTVSetActorSpec
. A feature clause describes a feature of the subject (class or other entity) you are specifying and testing. In the previous example, the subject under specification and test is a TV set. The feature being specified and tested is the behavior of a TV set when its power button is pressed. With each scenario you provide a string (the spec text) that specifies the behavior of the subject for one scenario in which the feature may be used, and a block of code that tests that behavior. You place the spec text between the parentheses, followed by the test code between curly braces. The test code will be wrapped up as a function passed as a by-name parameter toscenario
, which will register the test for later execution. The result type of the by-name in anAsyncFeatureSpec
must beFuture[Assertion]
.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, isFuture[Assertion]
. When anAsyncFeatureSpec
is constructed, any test that results inAssertion
will be implicitly converted toFuture[Assertion]
and registered. The implicit conversion is fromAssertion
toFuture[Assertion]
only, so you must end synchronous tests in some ScalaTest assertion or matcher expression. If a test would not otherwise end in typeAssertion
, you can placesucceed
at the end of the test.succeed
, a field in traitAssertions
, returns theSucceeded
singleton:scala> succeed res2: org.scalatest.Assertion = Succeeded
Thus placing
succeed
at the end of a test body will satisfy the type checker.An
AsyncFeatureSpec
's lifecycle has two phases: the registration phase and the ready phase. It starts in registration phase and enters ready phase the first timerun
is called on it. It then remains in ready phase for the remainder of its lifetime.Scenarios can only be registered with the
scenario
method while theAsyncFeatureSpec
is in its registration phase. Any attempt to register a scenario after theAsyncFeatureSpec
has entered its ready phase, i.e., afterrun
has been invoked on theAsyncFeatureSpec
, will be met with a thrownTestRegistrationClosedException
. The recommended style of usingAsyncFeatureSpec
is to register tests during object construction as is done in all the examples shown here. If you keep to the recommended style, you should never see aTestRegistrationClosedException
.Each scenario represents one test. The name of the test is the spec text passed to the
scenario
method. The feature name does not appear as part of the test name. In aAsyncFeatureSpec
, therefore, you must take care to ensure that each test has a unique name (in other words, that eachscenario
has unique spec text).When you run a
AsyncFeatureSpec
, it will sendFormatter
s in the events it sends to theReporter
. ScalaTest's built-in reporters will report these events in such a way that the output is easy to read as an informal specification of the subject being tested. For example, were you to runTVSetSpec
from within the Scala interpreter:scala> org.scalatest.run(new TVSetActorSpec)
You would see:
TVSetActorSpec: As a TV set owner I want to be able to turn the TV on and off So I can watch TV when I want And save energy when I'm not watching TV Feature: TV power button Scenario: User presses power button when TV is off Given a TV set that is switched off When the power button is pressed Then the TV should switch on Scenario: User presses power button when TV is on Given a TV set that is switched on When the power button is pressed Then the TV should switch off
Or, to run just the “
Feature: TV power button Scenario: User presses power button when TV is on
” method, you could pass that test's name, or any unique substring of the name, such as"TV is on"
. Here's an example:scala> org.scalatest.run(new TVSetActorSpec, "TV is on") TVSetActorSpec: As a TV set owner I want to be able to turn the TV on and off So I can watch TV when I want And save energy when I'm not watching TV Feature: TV power button Scenario: User presses power button when TV is on Given a TV set that is switched on When the power button is pressed Then the TV should switch off
Asynchronous execution model
AsyncFeatureSpec
extendsAsyncTestSuite
, which provides an implicitscala.concurrent.ExecutionContext
namedexecutionContext
. This execution context is used byAsyncFeatureSpec
to transform theFuture[Assertion]
s returned by each test into theFutureOutcome
returned by thetest
function passed towithFixture
. ThisExecutionContext
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 theFutureOutcome
returned from thetest
function. Other tasks that will be queued are any transformations of, or callbacks registered on,Future
s that occur in your test body, including any assertions you map ontoFuture
s. 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 ofFuture
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]
. ThisFuture[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 theFuture[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
FeatureSpec
withScalaFutures
instead. Alternatively, you could override theexecutionContext
and use a traditionalExecutionContext
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 therunNow
execution context on Scala.js instead of the defaultqueue
, you would write:// on Scala.js implicit override def executionContext = org.scalatest.concurrent.TestExecutionContext.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 anAsyncFeatureSpec
will be executed one after another, i.e., serially. This is true whether those tests returnAssertion
orFuture[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
AsyncFeatureSpec
to be executed in parallel, you must mix inParallelTestExecution
and enable parallel execution of tests in your build. You enable parallel execution inRunner
with the-P
command line flag. In the ScalaTest Maven Plugin, setparallel
totrue
. Insbt
, 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 theDistributor
, and allowed to complete in parallel, using threads from theexecutionContext
. If you are using ScalaTest's serial execution context, the JVM default, asynchronous tests will run in parallel very much like traditional (such asFeatureSpec
) tests run in parallel: 1) BecauseParallelTestExecution
extendsOneInstancePerTest
, 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 ofFuture
s 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 invokedrun
, 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 theexecutionContext
. 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 inParallelTestExecution
is executed: the tests will run sequentially. If you use an execution context backed by a thread-pool, such asglobal
, 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
andrecoverToExceptionIf
methods of traitRecoverMethods
. Because this trait is mixed into supertraitAsyncTestSuite
, both of these methods are available by default in anAsyncFeatureSpec
.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 toassertThrows
, except in the context of a future. It transforms aFuture
of any type into aFuture[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 therecoverToSucceededIf
in its behavior when the assertion succeeds:recoverToSucceededIf
yields aFuture[Assertion]
, whereasrecoverToExceptionIf
yields aFuture[T]
, whereT
is the expected exception type.recoverToExceptionIf[IllegalStateException] { // Result type: Future[IllegalStateException] emptyStackActor ? Peek }
In other words,
recoverToExpectionIf
is tointercept
asrecovertToSucceededIf
is toassertThrows
. 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 showingrecoverToExceptionIf
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]"))
Ignored tests
To support the common use case of temporarily disabling a test, with the good intention of resurrecting the test at a later time,
AsyncFeatureSpec
provides registration methods that start withignore
instead ofscenario
. Here's an example:package org.scalatest.examples.asyncfeaturespec.ignore
import org.scalatest.AsyncFeatureSpec import scala.concurrent.Future
class AddSpec extends AsyncFeatureSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum } def addNow(addends: Int*): Int = addends.sum
feature("The add methods") {
ignore("addSoon will eventually compute a sum of passed Ints") { 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) } }
scenario("addNow will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } } }If you run class
AddSpec
with:scala> org.scalatest.run(new AddSpec)
It will run only the second test and report that the first test was ignored:
AddSpec: Feature: The add methods - Scenario: addSoon will eventually compute a sum of passed Ints !!! IGNORED !!! - Scenario: addNow will immediately compute a sum of passed Ints
If you wish to temporarily ignore an entire suite of tests, you can (on the JVM, not Scala.js) annotate the test class with
@Ignore
, like this:package org.scalatest.examples.asyncfeaturespec.ignoreall
import org.scalatest.AsyncFeatureSpec import scala.concurrent.Future import org.scalatest.Ignore
@Ignore class AddSpec extends AsyncFeatureSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum } def addNow(addends: Int*): Int = addends.sum
feature("The add methods") {
scenario("addSoon will eventually compute a sum of passed Ints") { 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) } }
scenario("addNow will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } } }When you mark a test class with a tag annotation, ScalaTest will mark each test defined in that class with that tag. Thus, marking the
AddSpec
in the above example with the@Ignore
tag annotation means that both tests in the class will be ignored. If you run the aboveAddSpec
in the Scala interpreter, you'll see:AddSpec: Feature: The add methods - Scenario: addSoon will eventually compute a sum of passed Ints !!! IGNORED !!! - Scenario: addNow will immediately compute a sum of passed Ints !!! IGNORED !!!
Note that marking a test class as ignored won't prevent it from being discovered by ScalaTest. Ignored classes will be discovered and run, and all their tests will be reported as ignored. This is intended to keep the ignored class visible, to encourage the developers to eventually fix and “un-ignore” it. If you want to prevent a class from being discovered at all (on the JVM, not Scala.js), use the
DoNotDiscover
annotation instead.If you want to ignore all tests of a suite on Scala.js, where annotations can't be inspected at runtime, you'll need to change
it
toignore
at each test site. To make a suite non-discoverable on Scala.js, ensure it does not declare a public no-arg constructor. You can either declare a public constructor that takes one or more arguments, or make the no-arg constructor non-public. Because this technique will also make the suite non-discoverable on the JVM, it is a good approach for suites you want to run (but not be discoverable) on both Scala.js and the JVM.Informers
One of the parameters to
AsyncFeatureSpec
'srun
method is aReporter
, which will collect and report information about the running suite of tests. Information about suites and tests that were run, whether tests succeeded or failed, and tests that were ignored will be passed to theReporter
as the suite runs. Most often the default reporting done byAsyncFeatureSpec
's methods will be sufficient, but occasionally you may wish to provide custom information to theReporter
from a test. For this purpose, anInformer
that will forward information to the currentReporter
is provided via theinfo
parameterless method. You can pass the extra information to theInformer
via itsapply
method. TheInformer
will then pass the information to theReporter
via anInfoProvided
event.One use case for the
Informer
is to pass more information about a scenario to the reporter. For example, theGivenWhenThen
trait provides methods that use the implicitinfo
provided byAsyncFeatureSpec
to pass such information to the reporter. You can see this in action in the initial example of this trait's documentation.Documenters
AsyncFeatureSpec
also provides amarkup
method that returns aDocumenter
, which allows you to send to theReporter
text formatted in Markdown syntax. You can pass the extra information to theDocumenter
via itsapply
method. TheDocumenter
will then pass the information to theReporter
via anMarkupProvided
event.Here's an example
FlatSpec
that usesmarkup
:package org.scalatest.examples.asyncfeaturespec.markup
import collection.mutable import org.scalatest._
class SetSpec extends AsyncFeatureSpec with GivenWhenThen {
markup { """ Mutable Set ———-- A set is a collection that contains no duplicate elements. To implement a concrete mutable set, you need to provide implementations of the following methods: def contains(elem: A): Boolean def iterator: Iterator[A] def += (elem: A): this.type def -= (elem: A): this.type If you wish that methods like `take`, `drop`, `filter` return the same kind of set, you should also override: def empty: This It is also good idea to override methods `foreach` and `size` for efficiency. """ }
feature("An element can be added to an empty mutable Set") { scenario("When an element is added to an empty mutable Set") { Given("an empty mutable Set") val set = mutable.Set.empty[String]
When("an element is added") set += "clarity"
Then("the Set should have size 1") assert(set.size === 1)
And("the Set should contain the added element") assert(set.contains("clarity"))
markup("This test finished with a **bold** statement!") succeed } } }Although all of ScalaTest's built-in reporters will display the markup text in some form, the HTML reporter will format the markup information into HTML. Thus, the main purpose of
markup
is to add nicely formatted text to HTML reports. Here's what the aboveSetSpec
would look like in the HTML reporter:Notifiers and alerters
ScalaTest records text passed to
info
andmarkup
during tests, and sends the recorded text in therecordedEvents
field of test completion events likeTestSucceeded
andTestFailed
. This allows string reporters (like the standard out reporter) to showinfo
andmarkup
text after the test name in a color determined by the outcome of the test. For example, if the test fails, string reporters will show theinfo
andmarkup
text in red. If a test succeeds, string reporters will show theinfo
andmarkup
text in green. While this approach helps the readability of reports, it means that you can't useinfo
to get status updates from long running tests.To get immediate (i.e., non-recorded) notifications from tests, you can use
note
(aNotifier
) andalert
(anAlerter
). Here's an example showing the differences:package org.scalatest.examples.asyncfeaturespec.note
import collection.mutable import org.scalatest._
class SetSpec extends AsyncFeatureSpec {
feature("An element can be added to an empty mutable Set") { scenario("When an element is added to an empty mutable Set") {
info("info is recorded") markup("markup is *also* recorded") note("notes are sent immediately") alert("alerts are also sent immediately")
val set = mutable.Set.empty[String] set += "clarity" assert(set.size === 1) assert(set.contains("clarity")) } } }Because
note
andalert
information is sent immediately, it will appear before the test name in string reporters, and its color will be unrelated to the ultimate outcome of the test:note
text will always appear in green,alert
text will always appear in yellow. Here's an example:scala> org.scalatest.run(new SetSpec) SetSpec: Feature: An element can be added to an empty mutable Set + notes are sent immediately + alerts are also sent immediately Scenario: When an element is added to an empty mutable Set info is recorded + markup is *also* recorded
Another example is slowpoke notifications. If you find a test is taking a long time to complete, but you're not sure which test, you can enable slowpoke notifications. ScalaTest will use an
Alerter
to fire an event whenever a test has been running longer than a specified amount of time.In summary, use
info
andmarkup
for text that should form part of the specification output. Usenote
andalert
to send status notifications. (Because the HTML reporter is intended to produce a readable, printable specification,info
andmarkup
text will appear in the HTML report, butnote
andalert
text will not.)Pending tests
A pending test is one that has been given a name but is not yet implemented. The purpose of pending tests is to facilitate a style of testing in which documentation of behavior is sketched out before tests are written to verify that behavior (and often, before the behavior of the system being tested is itself implemented). Such sketches form a kind of specification of what tests and functionality to implement later.
To support this style of testing, a test can be given a name that specifies one bit of behavior required by the system being tested. At the end of the test, it can call method
pending
, which will cause it to complete abruptly withTestPendingException
.Because tests in ScalaTest can be designated as pending with
TestPendingException
, both the test name and any information sent to the reporter when running the test can appear in the report of a test run. (In other words, the code of a pending test is executed just like any other test.) However, because the test completes abruptly withTestPendingException
, the test will be reported as pending, to indicate the actual test, and possibly the functionality, has not yet been implemented. Here's an example:package org.scalatest.examples.asyncfeaturespec.pending
import org.scalatest.AsyncFeatureSpec import scala.concurrent.Future
class AddSpec extends AsyncFeatureSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum } def addNow(addends: Int*): Int = addends.sum
feature("The add methods") {
scenario("addSoon will eventually compute a sum of passed Ints") (pending)
scenario("addNow will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } } }(Note: "
(pending)
" is the body of the test. Thus the test contains just one statement, an invocation of thepending
method, which throwsTestPendingException
.) If you run this version ofAddSpec
with:scala> org.scalatest.run(new AddSpec)
It will run both tests, but report that first test is pending. You'll see:
AddSpec: Feature: The add methods - Scenario: addSoon will eventually compute a sum of passed Ints (pending) - Scenario: addNow will immediately compute a sum of passed Ints
One difference between an ignored test and a pending one is that an ignored test is intended to be used during significant refactorings of the code under test, when tests break and you don't want to spend the time to fix all of them immediately. You can mark some of those broken tests as ignored temporarily, so that you can focus the red bar on just failing tests you actually want to fix immediately. Later you can go back and fix the ignored tests. In other words, by ignoring some failing tests temporarily, you can more easily notice failed tests that you actually want to fix. By contrast, a pending test is intended to be used before a test and/or the code under test is written. Pending indicates you've decided to write a test for a bit of behavior, but either you haven't written the test yet, or have only written part of it, or perhaps you've written the test but don't want to implement the behavior it tests until after you've implemented a different bit of behavior you realized you need first. Thus ignored tests are designed to facilitate refactoring of existing code whereas pending tests are designed to facilitate the creation of new code.
One other difference between ignored and pending tests is that ignored tests are implemented as a test tag that is excluded by default. Thus an ignored test is never executed. By contrast, a pending test is implemented as a test that throws
TestPendingException
(which is what calling thepending
method does). Thus the body of pending tests are executed up until they throwTestPendingException
.Tagging tests
An
AsyncFeatureSpec
's tests may be classified into groups by tagging them with string names. As with any suite, when executing anAsyncFeatureSpec
, groups of tests can optionally be included and/or excluded. To tag anAsyncFeatureSpec
's tests, you pass objects that extend classorg.scalatest.Tag
to methods that register tests. ClassTag
takes one parameter, a string name. If you have created tag annotation interfaces as described in theTag
documentation, then you will probably want to use tag names on your test functions that match. To do so, simply pass the fully qualified names of the tag interfaces to theTag
constructor. For example, if you've defined a tag annotation interface with fully qualified name,com.mycompany.tags.DbTest
, then you could create a matching tag forAsyncFeatureSpec
s like this:package org.scalatest.examples.asyncfeaturespec.tagging
import org.scalatest.Tag
object DbTest extends Tag("com.mycompany.tags.DbTest")Given these definitions, you could place
AsyncFeatureSpec
tests into groups with tags like this:import org.scalatest.AsyncFeatureSpec import org.scalatest.tagobjects.Slow import scala.concurrent.Future
class AddSpec extends AsyncFeatureSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum } def addNow(addends: Int*): Int = addends.sum
feature("The add methods") {
scenario("addSoon will eventually compute a sum of passed Ints", Slow) {
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) } }
scenario("addNow will immediately compute a sum of passed Ints", Slow, DbTest) {
val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } } }This code marks both tests with the
org.scalatest.tags.Slow
tag, and the second test with thecom.mycompany.tags.DbTest
tag.The
run
method takes aFilter
, whose constructor takes an optionalSet[String]
calledtagsToInclude
and aSet[String]
calledtagsToExclude
. IftagsToInclude
isNone
, all tests will be run except those those belonging to tags listed in thetagsToExclude
Set
. IftagsToInclude
is defined, only tests belonging to tags mentioned in thetagsToInclude
set, and not mentioned intagsToExclude
, will be run.It is recommended, though not required, that you create a corresponding tag annotation when you create a
Tag
object. A tag annotation (on the JVM, not Scala.js) allows you to tag all the tests of anAsyncFeatureSpec
in one stroke by annotating the class. For more information and examples, see the documentation for classTag
. On Scala.js, to tag all tests of a suite, you'll need to tag each test individually at the test site.Shared fixtures
A test fixture is composed of the objects and other artifacts (files, sockets, database connections, etc.) tests use to do their work. When multiple tests need to work with the same fixtures, it is important to try and avoid duplicating the fixture code across those tests. The more code duplication you have in your tests, the greater drag the tests will have on refactoring the actual production code.
ScalaTest recommends three techniques to eliminate such code duplication in async styles:
- Refactor using Scala
- Override
withFixture
- Mix in a before-and-after trait
Each technique is geared towards helping you reduce code duplication without introducing instance
var
s, shared mutable objects, or other dependencies between tests. Eliminating shared mutable state across tests will make your test code easier to reason about and eliminate the need to synchronize access to shared mutable state on the JVM.The following sections describe these techniques, including explaining the recommended usage for each. But first, here's a table summarizing the options:
Refactor using Scala when different tests need different fixtures. get-fixture methods The extract method refactor helps you create a fresh instances of mutable fixture objects in each test that needs them, but doesn't help you clean them up when you're done. loan-fixture methods Factor out dupicate code with the loan pattern when different tests need different fixtures that must be cleaned up afterwards. Override withFixture
when most or all tests need the same fixture.withFixture(NoArgAsyncTest)
The recommended default approach when most or all tests need the same fixture treatment. This general technique allows you, for example, to perform side effects at the beginning and end of all or most tests, transform the outcome of tests, retry tests, make decisions based on test names, tags, or other test data. Use this technique unless: - Different tests need different fixtures (refactor using Scala instead)
- An exception in fixture code should abort the suite, not fail the test (use a before-and-after trait instead)
- You have objects to pass into tests (override
withFixture(OneArgAsyncTest)
instead)
withFixture(OneArgAsyncTest)
Use when you want to pass the same fixture object or objects as a parameter into all or most tests. Mix in a before-and-after trait when you want an aborted suite, not a failed test, if the fixture code fails. BeforeAndAfter
Use this boilerplate-buster when you need to perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. BeforeAndAfterEach
Use when you want to stack traits that perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. Calling get-fixture methods
If you need to create the same mutable fixture objects in multiple tests, and don't need to clean them up after using them, the simplest approach is to write one or more get-fixture methods. A get-fixture method returns a new instance of a needed fixture object (or a holder object containing multiple fixture objects) each time it is called. You can call a get-fixture method at the beginning of each test that needs the fixture, storing the returned object or objects in local variables. Here's an example:
package org.scalatest.examples.asyncfeaturespec.getfixture
import org.scalatest.AsyncFeatureSpec import scala.concurrent.Future
class ExampleSpec extends AsyncFeatureSpec {
def fixture: Future[String] = Future { "ScalaTest is designed to " }
feature("Simplicity") { scenario("User needs to read test code written by others") { val future = fixture val result = future map { s => s + "encourage clear code!" } result map { s => assert(s == "ScalaTest is designed to encourage clear code!") } }
scenario("User needs to understand what the tests are doing") { val future = fixture val result = future map { s => s + "be easy to reason about!" } result map { s => assert(s == "ScalaTest is designed to be easy to reason about!") } } } }If you need to configure fixture objects differently in different tests, you can pass configuration into the get-fixture method. For example, you could pass in an initial value for a fixture object as a parameter to the get-fixture method.
Overriding
withFixture(NoArgAsyncTest)
Although the get-fixture method approach takes care of setting up a fixture at the beginning of each test, it doesn't address the problem of cleaning up a fixture at the end of the test. If you just need to perform a side-effect at the beginning or end of a test, and don't need to actually pass any fixture objects into the test, you can override
withFixture(NoArgAsyncTest)
, a method defined in traitAsyncTestSuite
, a supertrait ofAsyncFeatureSpec
.Trait
AsyncFeatureSpec
'srunTest
method passes a no-arg async test function towithFixture(NoArgAsyncTest)
. It iswithFixture
's responsibility to invoke that test function. The default implementation ofwithFixture
simply invokes the function and returns the result, like this:// 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 thecomplete
-lastly
syntax, defined in supertraitCompleteLastly
. Thecomplete
-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 thesuper
implementation ofwithFixture
, 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: NoArgTest) = {
// 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: NoArgTest) = {
// 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 ofFuture
's registration methods:onComplete
,onSuccess
, oronFailure
. Note that if a test fails, that will be treated as ascala.util.Success(org.scalatest.Failed)
. So if you want to perform an action if a test fails, for example, you'd register the callback usingonSuccess
.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.asyncfeaturespec.noargasynctest
import java.io.File import org.scalatest._ import scala.concurrent.Future
class ExampleSpec extends AsyncFeatureSpec {
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 }
feature("addSoon") { scenario("succeed case") { addSoon(1, 1) map { sum => assert(sum == 2) } }
scenario("fail case") { addSoon(1, 1) map { sum => assert(sum == 3) } } } }Running this version of
ExampleSpec
in the interpreter in a directory with two files,hello.txt
andworld.txt
would give the following output:scala> org.scalatest.run(new ExampleSpec) ExampleSpec: Feature: addSoon - Scenario: succeed case - Scenario: fail case *** FAILED *** 2 did not equal 3 (
:33) Note that the
NoArgAsyncTest
passed towithFixture
, in addition to anapply
method that executes the test, also includes the test name and the config map passed torunTest
. Thus you can also use the test name and configuration objects in yourwithFixture
implementation.Lastly, if you want to transform the outcome in some way in
withFixture
, you'll need to use either themap
ortransform
methods ofFuture
, 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
'sapply
method will return ascala.util.Failure
only if the test completes abruptly with a "test-fatal" exception (such asOutOfMemoryError
) that should cause the suite to abort rather than the test to fail. Thus usually you would usemap
to transform future outcomes, nottransform
, so that such test-fatal exceptions pass through unchanged. The suite will abort asynchronously with any exception returned fromNoArgAsyncTest
's apply method in ascala.util.Failure
.Calling loan-fixture methods
If you need to both pass a fixture object into a test and perform cleanup at the end of the test, you'll need to use the loan pattern. If different tests need different fixtures that require cleanup, you can implement the loan pattern directly by writing loan-fixture methods. A loan-fixture method takes a function whose body forms part or all of a test's code. It creates a fixture, passes it to the test code by invoking the function, then cleans up the fixture after the function returns.
The following example shows three tests that use two fixtures, a database and a file. Both require cleanup after, so each is provided via a loan-fixture method. (In this example, the database is simulated with a
StringBuffer
.)package org.scalatest.examples.asyncfeaturespec.loanfixture
import java.util.concurrent.ConcurrentHashMap
import scala.concurrent.Future import scala.concurrent.ExecutionContext
object DbServer { // Simulating a database server type Db = StringBuffer private final val databases = new ConcurrentHashMap[String, Db] def createDb(name: String): Db = { val db = new StringBuffer // java.lang.StringBuffer is thread-safe databases.put(name, db) db } def removeDb(name: String): Unit = { databases.remove(name) } }
// 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 } } }
import org.scalatest._ import DbServer._ import java.util.UUID.randomUUID
class ExampleSpec extends AsyncFeatureSpec {
def withDatabase(testCode: Future[Db] => Future[Assertion]) = { val dbName = randomUUID.toString // generate a unique db name val futureDb = Future { createDb(dbName) } // create the fixture complete { val futurePopulatedDb = futureDb map { db => db.append("ScalaTest is designed to ") // perform setup } testCode(futurePopulatedDb) // "loan" the fixture to the test code } lastly { removeDb(dbName) // ensure the fixture will be cleaned up } }
def withActor(testCode: StringActor => Future[Assertion]) = { val actor = new StringActor complete { actor ! Append("ScalaTest is designed to ") // set up the fixture testCode(actor) // "loan" the fixture to the test code } lastly { actor ! Clear // ensure the fixture will be cleaned up } }
feature("Simplicity") { // This test needs the actor fixture scenario("User needs to read test code written by others") { withActor { actor => actor ! Append("encourage clear code!") val futureString = actor ? GetValue futureString map { s => assert(s === "ScalaTest is designed to encourage clear code!") } } } // This test needs the database fixture scenario("User needs to understand what the tests are doing") { withDatabase { futureDb => futureDb map { db => db.append("be easy to reason about!") assert(db.toString === "ScalaTest is designed to be easy to reason about!") } } } // This test needs both the actor and the database scenario("User needs to write tests") { withDatabase { futureDb => withActor { actor => // loan-fixture methods compose actor ! Append("be easy to remember how to write!") val futureString = actor ? GetValue val futurePair: Future[(Db, String)] = futureDb zip futureString futurePair map { case (db, s) => db.append("be easy to learn!") assert(db.toString === "ScalaTest is designed to be easy to learn!") assert(s === "ScalaTest is designed to be easy to remember how to write!") } } } } } }As demonstrated by the last test, loan-fixture methods compose. Not only do loan-fixture methods allow you to give each test the fixture it needs, they allow you to give a test multiple fixtures and clean everything up afterwards.
Also demonstrated in this example is the technique of giving each test its own "fixture sandbox" to play in. When your fixtures involve external side-effects, like creating databases, it is a good idea to give each database a unique name as is done in this example. This keeps tests completely isolated, allowing you to run them in parallel if desired.
Overriding
withFixture(OneArgTest)
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 overridingwithFixture(OneArgAsyncTest)
. Each test in afixture.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 specifyingFixtureParam
, and implement awithFixture
method that takes aOneArgAsyncTest
. ThiswithFixture
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 letwithFixture(NoArgAsyncTest)
invoke the test function instead of invoking the test function directly. To do so, you'll need to convert theOneArgAsyncTest
to aNoArgAsyncTest
. You can do that by passing the fixture object to thetoNoArgAsyncTest
method ofOneArgAsyncTest
. In other words, instead of writing “test(theFixture)
”, you'd delegate responsibility for invoking the test function to thewithFixture(NoArgAsyncTest)
method of the same instance by writing:withFixture(test.toNoArgAsyncTest(theFixture))
Here's a complete example:
package org.scalatest.examples.asyncfeaturespec.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.AsyncFeatureSpec {
type FixtureParam = StringActor
def withFixture(test: OneArgAsyncTest): FutureOutcome = {
val actor = new StringActor complete { actor ! Append("ScalaTest is designed to ") // set up the fixture withFixture(test.toNoArgAsyncTest(actor)) } lastly { actor ! Clear // ensure the fixture will be cleaned up } }
feature("Simplicity") { scenario("User needs to read test code written by others") { actor => actor ! Append("encourage clear code!") val futureString = actor ? GetValue futureString map { s => assert(s === "ScalaTest is designed to encourage clear code!") } }
scenario("User needs to understand what the tests are doing") { actor => actor ! Append("be easy to reason about!") val futureString = actor ? GetValue futureString map { s => assert(s === "ScalaTest is designed to be easy to reason about!") } } } }In this example, the tests required one fixture object, a
StringActor
. If your tests need multiple fixture objects, you can simply define theFixtureParam
type to be a tuple containing the objects or, alternatively, a case class containing the objects. For more information on thewithFixture(OneArgAsyncTest)
technique, see the documentation forfixture.AsyncFeatureSpec
.Mixing in
BeforeAndAfter
In all the shared fixture examples shown so far, the activities of creating, setting up, and cleaning up the fixture objects have been performed during the test. This means that if an exception occurs during any of these activities, it will be reported as a test failure. Sometimes, however, you may want setup to happen before the test starts, and cleanup after the test has completed, so that if an exception occurs during setup or cleanup, the entire suite aborts and no more tests are attempted. The simplest way to accomplish this in ScalaTest is to mix in trait
BeforeAndAfter
. With this trait you can denote a bit of code to run before each test withbefore
and/or after each test each test withafter
, like this:package org.scalatest.examples.asyncfeaturespec.beforeandafter
import org.scalatest.AsyncFeatureSpec import org.scalatest.BeforeAndAfter 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 AsyncFeatureSpec with BeforeAndAfter {
final val actor = new StringActor
before { actor ! Append("ScalaTest is designed to ") // set up the fixture }
after { actor ! Clear // clean up the fixture }
feature("Simplicity") { scenario("User needs to read test code written by others") { actor ! Append("encourage clear code!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is designed to encourage clear code!") } }
scenario("User needs to understand what the tests are doing") { actor ! Append("be easy to reason about!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is designed to be easy to reason about!") } } } }Note that the only way
before
andafter
code can communicate with test code is via some side-effecting mechanism, commonly by reassigning instancevar
s or by changing the state of mutable objects held from instanceval
s (as in this example). If using instancevar
s or mutable objects held from instanceval
s you wouldn't be able to run tests in parallel in the same instance of the test class (on the JVM, not Scala.js) unless you synchronized access to the shared, mutable state.Note that on the JVM, if you override ScalaTest's default serial execution context, you will likely need to worry about synchronizing access to shared mutable fixture state, because the execution context may assign different threads to process different
Future
transformations. Although access to mutable state along the same linear chain ofFuture
transformations need not be synchronized, it can be difficult to spot cases where these constraints are violated. The best approach is to use only immutable objects when transformingFuture
s. When that's not practical, involve only thread-safe mutable objects, as is done in the above example. On Scala.js, by contrast, you need not worry about thread synchronization, because in effect only one thread exists.Although
BeforeAndAfter
provides a minimal-boilerplate way to execute code before and after tests, it isn't designed to enable stackable traits, because the order of execution would be non-obvious. If you want to factor out before and after code that is common to multiple test suites, you should use traitBeforeAndAfterEach
instead, as shown later in the next section, composing fixtures by stacking traits.Composing fixtures by stacking traits
In larger projects, teams often end up with several different fixtures that test classes need in different combinations, and possibly initialized (and cleaned up) in different orders. A good way to accomplish this in ScalaTest is to factor the individual fixtures into traits that can be composed using the stackable trait pattern. This can be done, for example, by placing
withFixture
methods in several traits, each of which callsuper.withFixture
. Here's an example in which theStringBuilderActor
andStringBufferActor
fixtures used in the previous examples have been factored out into two stackable fixture traits namedBuilder
andBuffer
:package org.scalatest.examples.asyncfeaturespec.composingwithasyncfixture
import org.scalatest._ import org.scalatest.SuiteMixin import collection.mutable.ListBuffer 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 StringBuilderActor { // 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 StringBufferActor { private final val buf = ListBuffer.empty[String] def !(op: StringOp): Unit = synchronized { op match { case Append(value) => buf += value case Clear => buf.clear() } } def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[List[String]] = Future { synchronized { buf.toList } } }
trait Builder extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val builderActor = new StringBuilderActor
abstract override def withFixture(test: NoArgAsyncTest) = { builderActor ! Append("ScalaTest is designed to ") complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { builderActor ! Clear } } }
trait Buffer extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val bufferActor = new StringBufferActor
abstract override def withFixture(test: NoArgAsyncTest) = { complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { bufferActor ! Clear } } }
class ExampleSpec extends AsyncFeatureSpec with Builder with Buffer {
feature("Simplicity") { scenario("User needs to read test code written by others") { builderActor ! Append("encourage clear code!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is designed to encourage clear code!") assert(lst.isEmpty) bufferActor ! Append("sweet") succeed } }
scenario("User needs to understand what the tests are doing") { builderActor ! Append("be easy to reason about!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is designed to be easy to reason about!") assert(lst.isEmpty) bufferActor ! Append("awesome") succeed } } } }By mixing in both the
Builder
andBuffer
traits,ExampleSpec
gets both fixtures, which will be initialized before each test and cleaned up after. The order the traits are mixed together determines the order of execution. In this case,Builder
is “super” toBuffer
. If you wantedBuffer
to be “super” toBuilder
, you need only switch the order you mix them together, like this:class Example2Spec extends AsyncFeatureSpec with Buffer with Builder
If you only need one fixture you mix in only that trait:
class Example3Spec extends AsyncFeatureSpec with Builder
Another way to create stackable fixture traits is by extending the
BeforeAndAfterEach
and/orBeforeAndAfterAll
traits.BeforeAndAfterEach
has abeforeEach
method that will be run before each test (like JUnit'ssetUp
), and anafterEach
method that will be run after (like JUnit'stearDown
). Similarly,BeforeAndAfterAll
has abeforeAll
method that will be run before all tests, and anafterAll
method that will be run after all tests. Here's what the previously shown example would look like if it were rewritten to use theBeforeAndAfterEach
methods instead ofwithFixture
:package org.scalatest.examples.asyncfeaturespec.composingbeforeandaftereach
import org.scalatest._ import org.scalatest.BeforeAndAfterEach import collection.mutable.ListBuffer 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 StringBuilderActor { // 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 StringBufferActor { private final val buf = ListBuffer.empty[String] def !(op: StringOp): Unit = synchronized { op match { case Append(value) => buf += value case Clear => buf.clear() } } def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[List[String]] = Future { synchronized { buf.toList } } }
trait Builder extends BeforeAndAfterEach { this: Suite =>
final val builderActor = new StringBuilderActor
override def beforeEach() { builderActor ! Append("ScalaTest is designed to ") super.beforeEach() // To be stackable, must call super.beforeEach }
override def afterEach() { try super.afterEach() // To be stackable, must call super.afterEach finally builderActor ! Clear } }
trait Buffer extends BeforeAndAfterEach { this: Suite =>
final val bufferActor = new StringBufferActor
override def afterEach() { try super.afterEach() // To be stackable, must call super.afterEach finally bufferActor ! Clear } }
class ExampleSpec extends AsyncFeatureSpec with Builder with Buffer {
feature("Simplicity") {
scenario("User needs to read test code written by others") { builderActor ! Append("encourage clear code!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is designed to encourage clear code!") assert(lst.isEmpty) bufferActor ! Append("sweet") succeed } }
scenario("User needs to understand what the tests are doing") { builderActor ! Append("be easy to reason about!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is designed to be easy to reason about!") assert(lst.isEmpty) bufferActor ! Append("awesome") succeed } } } }To get the same ordering as
withFixture
, place yoursuper.beforeEach
call at the end of eachbeforeEach
method, and thesuper.afterEach
call at the beginning of eachafterEach
method, as shown in the previous example. It is a good idea to invokesuper.afterEach
in atry
block and perform cleanup in afinally
clause, as shown in the previous example, because this ensures the cleanup code is performed even ifsuper.afterEach
throws an exception.The difference between stacking traits that extend
BeforeAndAfterEach
versus traits that implementwithFixture
is that setup and cleanup code happens before and after the test inBeforeAndAfterEach
, but at the beginning and end of the test inwithFixture
. Thus if awithFixture
method completes abruptly with an exception, it is considered a failed test. By contrast, if any of thebeforeEach
orafterEach
methods ofBeforeAndAfterEach
complete abruptly, it is considered an aborted suite, which will result in aSuiteAborted
event.Shared tests
Sometimes you may want to run the same test code on different fixture objects. In other words, you may want to write tests that are "shared" by different fixture objects. To accomplish this in an
AsyncFeatureSpec
, you first place shared tests in behavior functions. These behavior functions will be invoked during the construction phase of anyAsyncFeatureSpec
that uses them, so that the tests they contain will be registered as tests in thatAsyncFeatureSpec
. For example, given thisStackActor
class:package org.scalatest.examples.asyncfeaturespec.sharedtests
import scala.collection.mutable.ListBuffer import scala.concurrent.Future import scala.concurrent.ExecutionContext
// Stack operations case class Push[T](value: T) sealed abstract class StackOp case object Pop extends StackOp case object Peek extends StackOp case object Size extends StackOp
// Stack info case class StackInfo[T](top: Option[T], size: Int, max: Int) { require(size >= 0, "size was less than zero") require(max >= size, "max was less than size") val isFull: Boolean = size == max val isEmpty: Boolean = size == 0 }
class StackActor[T](Max: Int, name: String) {
private final val buf = new ListBuffer[T]
def !(push: Push[T]): Unit = synchronized { if (buf.size != Max) buf.prepend(push.value) else throw new IllegalStateException("can't push onto a full stack") }
def ?(op: StackOp)(implicit c: ExecutionContext): Future[StackInfo[T]] = synchronized { op match { case Pop => Future { if (buf.size != 0) StackInfo(Some(buf.remove(0)), buf.size, Max) else throw new IllegalStateException("can't pop an empty stack") } case Peek => Future { if (buf.size != 0) StackInfo(Some(buf(0)), buf.size, Max) else throw new IllegalStateException("can't peek an empty stack") } case Size => Future { StackInfo(None, buf.size, Max) } } }
override def toString: String = name }You may want to test the stack represented by the
StackActor
class in different states: empty, full, with one item, with one item less than capacity, etc. You may find you have several tests that make sense any time the stack is non-empty. Thus you'd ideally want to run those same tests for three stack fixture objects: a full stack, a stack with a one item, and a stack with one item less than capacity. With shared tests, you can factor these tests out into a behavior function, into which you pass the stack fixture to use when running the tests. So in yourAsyncFeatureSpec
forStackActor
, you'd invoke the behavior function three times, passing in each of the three stack fixtures so that the shared tests are run for all three fixtures.You can define a behavior function that encapsulates these shared tests inside the
AsyncFeatureSpec
that uses them. If they are shared between differentAsyncFeatureSpec
s, however, you could also define them in a separate trait that is mixed into eachAsyncFeatureSpec
that uses them. For example, here thenonEmptyStackActor
behavior function (in this case, a behavior method) is defined in a trait along with another method containing shared tests for non-full stacks:import org.scalatest.AsyncFeatureSpec
trait AsyncFeatureSpecStackBehaviors { this: AsyncFeatureSpec =>
def nonEmptyStackActor(createNonEmptyStackActor: => StackActor[Int], lastItemAdded: Int, name: String): Unit = {
scenario("Size is fired at non-empty stack actor: " + name) { val stackActor = createNonEmptyStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(!stackInfo.isEmpty) } }
scenario("Peek is fired at non-empty stack actor: " + name) { val stackActor = createNonEmptyStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePeek <- stackActor ? Size afterPeek <- stackActor ? Peek } yield (beforePeek, afterPeek) futurePair map { case (beforePeek, afterPeek) => assert(afterPeek.top == Some(lastItemAdded)) assert(afterPeek.size == beforePeek.size) } }
scenario("Pop is fired at non-empty stack actor: " + name) { val stackActor = createNonEmptyStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePop <- stackActor ? Size afterPop <- stackActor ? Pop } yield (beforePop, afterPop) futurePair map { case (beforePop, afterPop) => assert(afterPop.top == Some(lastItemAdded)) assert(afterPop.size == beforePop.size - 1) } } }
def nonFullStackActor(createNonFullStackActor: => StackActor[Int], name: String): Unit = {
scenario("Size is fired at non-full stack actor: " + name) { val stackActor = createNonFullStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(!stackInfo.isFull) } }
scenario("Push is fired at non-full stack actor: " + name) { val stackActor = createNonFullStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePush <- stackActor ? Size afterPush <- { stackActor ! Push(7); stackActor ? Peek } } yield (beforePush, afterPush) futurePair map { case (beforePush, afterPush) => assert(afterPush.size == beforePush.size + 1) assert(afterPush.top == Some(7)) } } } }Given these behavior functions, you could invoke them directly, but
AsyncFeatureSpec
offers a DSL for the purpose, which looks like this:scenariosFor(nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName)) scenariosFor(nonFullStackActor(almostEmptyStackActor, almostEmptyStackActorName))
Here's an example:
class StackSpec extends AsyncFeatureSpec with AsyncFeatureSpecStackBehaviors {
val Max = 10 val LastValuePushed = Max - 1
// Stack fixture creation methods val emptyStackActorName = "empty stack actor" def emptyStackActor = new StackActor[Int](Max, emptyStackActorName )
val fullStackActorName = "full stack actor" def fullStackActor = { val stackActor = new StackActor[Int](Max, fullStackActorName ) for (i <- 0 until Max) stackActor ! Push(i) stackActor }
val almostEmptyStackActorName = "almost empty stack actor" def almostEmptyStackActor = { val stackActor = new StackActor[Int](Max, almostEmptyStackActorName ) stackActor ! Push(LastValuePushed) stackActor }
val almostFullStackActorName = "almost full stack actor" def almostFullStackActor = { val stackActor = new StackActor[Int](Max, almostFullStackActorName) for (i <- 1 to LastValuePushed) stackActor ! Push(i) stackActor }
feature("A Stack is pushed and popped") {
scenario("Size is fired at empty stack actor") { val stackActor = emptyStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(stackInfo.isEmpty) } }
scenario("Peek is fired at empty stack actor") { recoverToSucceededIf[IllegalStateException] { emptyStackActor ? Peek } }
scenario("Pop is fired at empty stack actor") { recoverToSucceededIf[IllegalStateException] { emptyStackActor ? Pop } }
scenariosFor(nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName)) scenariosFor(nonFullStackActor(almostEmptyStackActor, almostEmptyStackActorName))
scenariosFor(nonEmptyStackActor(almostFullStackActor, LastValuePushed, almostFullStackActorName)) scenariosFor(nonFullStackActor(almostFullStackActor, almostFullStackActorName))
scenario("full is invoked on a full stack") { val stackActor = fullStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(stackInfo.isFull) } }
scenariosFor(nonEmptyStackActor(fullStackActor, LastValuePushed, fullStackActorName))
scenario("push is invoked on a full stack") { val stackActor = fullStackActor assertThrows[IllegalStateException] { stackActor ! Push(10) } } } }If you load these classes into the Scala interpreter (with scalatest's JAR file on the class path), and execute it, you'll see:
scala> org.scalatest.run(new StackSpec) StackSpec: Feature: A Stack actor - Scenario: Size is fired at empty stack actor - Scenario: Peek is fired at empty stack actor - Scenario: Pop is fired at empty stack actor - Scenario: Size is fired at non-empty stack actor: almost empty stack actor - Scenario: Peek is fired at non-empty stack actor: almost empty stack actor - Scenario: Pop is fired at non-empty stack actor: almost empty stack actor - Scenario: Size is fired at non-full stack actor: almost empty stack actor - Scenario: Push is fired at non-full stack actor: almost empty stack actor - Scenario: Size is fired at non-empty stack actor: almost full stack actor - Scenario: Peek is fired at non-empty stack actor: almost full stack actor - Scenario: Pop is fired at non-empty stack actor: almost full stack actor - Scenario: Size is fired at non-full stack actor: almost full stack actor - Scenario: Push is fired at non-full stack actor: almost full stack actor - Scenario: Size is fired at full stack actor - Scenario: Size is fired at non-empty stack actor: full stack actor - Scenario: Peek is fired at non-empty stack actor: full stack actor - Scenario: Pop is fired at non-empty stack actor: full stack actor - Scenario: Push is fired at full stack actor
One thing to keep in mind when using shared tests is that in ScalaTest, each test in a suite must have a unique name. If you register the same tests repeatedly in the same suite, one problem you may encounter is an exception at runtime complaining that multiple tests are being registered with the same test name. Although in an
AsyncFeatureSpec
, thefeature
clause is a nesting construct analogous toAsyncFunSpec
'sdescribe
clause, you many sometimes need to do a bit of extra work to ensure that the test names are unique. If a duplicate test name problem shows up in anAsyncFeatureSpec
, you'll need to pass in a prefix or suffix string to add to each test name. You can calltoString
on the shared fixture object, or pass this string the same way you pass any other data needed by the shared tests. This is the approach taken by the previousAsyncFeatureSpecStackBehaviors
example.Given this
AsyncFeatureSpecStackBehaviors
trait, calling it with thealmostEmptyStackActor
fixture, like this:scenariosFor(nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName))
yields test names:
Size is fired at non-empty stack actor: almost empty stack actor
Peek is fired at non-empty stack actor: almost empty stack actor
Pop is fired at non-empty stack actor: almost empty stack actor
Whereas calling it with the
almostFullStackActor
fixture, like this:scenariosFor(nonEmptyStack(almostFullStackActor, lastValuePushed, almostFullStackActorName))
yields different test names:
Size is fired at non-empty stack actor: almost full stack actor
Peek is fired at non-empty stack actor: almost full stack actor
Pop is fired at non-empty stack actor: almost full stack actor
- trait AsyncFeatureSpecLike extends AsyncTestSuite with AsyncTestRegistration with Informing with Notifying with Alerting with Documenting
Implementation trait for class
AsyncFeatureSpec
, which represents a suite of tests in which each test represents one scenario of a feature.Implementation trait for class
AsyncFeatureSpec
, which represents a suite of tests in which each test represents one scenario of a feature.AsyncFeatureSpec
is a class, not a trait, to minimize compile time given there is a slight compiler overhead to mixing in traits compared to extending classes. If you need to mix the behavior ofAsyncFeatureSpec
into some other class, you can use this trait instead, because classAsyncFeatureSpec
does nothing more than extend this trait and add a nicetoString
implementation.See the documentation of the class for a detailed overview of
AsyncFeatureSpec
. - abstract class AsyncFlatSpec extends AsyncFlatSpecLike
Enables testing of asynchronous code without blocking, using a style consistent with traditional
FlatSpec
tests.Enables testing of asynchronous code without blocking, using a style consistent with traditional
FlatSpec
tests.Recommended Usage: AsyncFlatSpec
is intended to enable users ofFlatSpec
to write non-blocking asynchronous tests that are consistent with their traditionalFlatSpec
tests. Note:AsyncFlatSpec
is intended for use in special situations where non-blocking asynchronous testing is needed, with classFlatSpec
used for general needs.Given a
Future
returned by the code you are testing, you need not block until theFuture
completes before performing assertions against its value. You can instead map those assertions onto theFuture
and return the resultingFuture[Assertion]
to ScalaTest. The test will complete asynchronously, when theFuture[Assertion]
completes.Trait
AsyncFlatSpec
is so named because your specification text and tests line up flat against the left-side indentation level, with no nesting needed. Here's an exampleAsyncFlatSpec
: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) } }The initial test in this example demonstrates the use of an explicit
behavior of
clause, which establishesaddSoon
as the subject. The second test demonstrates the alternate syntax of replacing the firstit
with the subject string, in this case,"addNow"
. As with traditionalFlatSpec
s, you can usemust
orcan
as well asshould
. For example, instead ofit should "eventually
..., you could writeit must "eventually
... orit can "eventually
.... You can also writethey
instead ofit
. See the documentation forFlatSpec
for more detail.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, isFuture[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 toFuture[Assertion]
and registered. The implicit conversion is fromAssertion
toFuture[Assertion]
, so you must end synchronous tests in some ScalaTest assertion or matcher expression. If a test would not otherwise end in typeAssertion
, you can placesucceed
at the end of the test.succeed
, a field in traitAssertions
, returns theSucceeded
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 }
An
AsyncFlatSpec
's lifecycle has two phases: the registration phase and the ready phase. It starts in registration phase and enters ready phase the first timerun
is called on it. It then remains in ready phase for the remainder of its lifetime.Tests can only be registered with the
it
method while theAsyncFlatSpec
is in its registration phase. Any attempt to register a test after theAsyncFlatSpec
has entered its ready phase, i.e., afterrun
has been invoked on theAsyncFlatSpec
, will be met with a thrownTestRegistrationClosedException
. The recommended style of usingAsyncFlatSpec
is to register tests during object construction as is done in all the examples shown here. If you keep to the recommended style, you should never see aTestRegistrationClosedException
.Asynchronous execution model
AsyncFlatSpec
extendsAsyncTestSuite
, which provides an implicitscala.concurrent.ExecutionContext
namedexecutionContext
. This execution context is used byAsyncFlatSpec
to transform theFuture[Assertion]
s returned by each test into theFutureOutcome
returned by thetest
function passed towithFixture
. ThisExecutionContext
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 theFutureOutcome
returned from thetest
function. Other tasks that will be queued are any transformations of, or callbacks registered on,Future
s that occur in your test body, including any assertions you map ontoFuture
s. 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 ofFuture
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]
. ThisFuture[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 theFuture[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
FlatSpec
withScalaFutures
instead. Alternatively, you could override theexecutionContext
and use a traditionalExecutionContext
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 therunNow
execution context on Scala.js instead of the defaultqueue
, you would write:// on Scala.js implicit override def executionContext = org.scalatest.concurrent.TestExecutionContext.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 anAsyncFlatSpec
will be executed one after another, i.e., serially. This is true whether those tests returnAssertion
orFuture[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
AsyncFlatSpec
to be executed in parallel, you must mix inParallelTestExecution
and enable parallel execution of tests in your build. You enable parallel execution inRunner
with the-P
command line flag. In the ScalaTest Maven Plugin, setparallel
totrue
. Insbt
, 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 theDistributor
, and allowed to complete in parallel, using threads from theexecutionContext
. If you are using ScalaTest's serial execution context, the JVM default, asynchronous tests will run in parallel very much like traditional (such asFlatSpec
) tests run in parallel: 1) BecauseParallelTestExecution
extendsOneInstancePerTest
, 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 ofFuture
s 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 invokedrun
, 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 theexecutionContext
. 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 inParallelTestExecution
is executed: the tests will run sequentially. If you use an execution context backed by a thread-pool, such asglobal
, 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
andrecoverToExceptionIf
methods of traitRecoverMethods
. Because this trait is mixed into supertraitAsyncTestSuite
, both of these methods are available by default in anAsyncFlatSpec
.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 toassertThrows
, except in the context of a future. It transforms aFuture
of any type into aFuture[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 therecoverToSucceededIf
in its behavior when the assertion succeeds:recoverToSucceededIf
yields aFuture[Assertion]
, whereasrecoverToExceptionIf
yields aFuture[T]
, whereT
is the expected exception type.recoverToExceptionIf[IllegalStateException] { // Result type: Future[IllegalStateException] emptyStackActor ? Peek }
In other words,
recoverToExpectionIf
is tointercept
asrecovertToSucceededIf
is toassertThrows
. 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 showingrecoverToExceptionIf
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]"))
Ignored tests
To support the common use case of temporarily disabling a test, with the good intention of resurrecting the test at a later time,
AsyncFlatSpec
provides two ways to ignore a test, both demonstrated in the following example:package org.scalatest.examples.asyncflatspec.ignore
import org.scalatest.AsyncFlatSpec import scala.concurrent.Future
class AddSpec extends AsyncFlatSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
behavior of "addSoon"
ignore 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" ignore { val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } }In the first test,
ignore
is used instead ofit
. In the second test, which uses the shorthand notation, noit
exists to change intoignore
. To ignore such tests, you must instead changein
toignore
, as shown in the above example. If you run this version ofAddSpec
with:scala> org.scalatest.run(new AddSpec)
It will report both tests as ignored:
AddSpec: addSoon - should eventually compute a sum of passed Ints !!! IGNORED !!! addNow - should immediately compute a sum of passed Ints !!! IGNORED !!!
If you wish to temporarily ignore an entire suite of tests, you can (on the JVM, not Scala.js) annotate the test class with
@Ignore
, like this:package org.scalatest.examples.asyncflatspec.ignoreall
import org.scalatest.AsyncFlatSpec import scala.concurrent.Future import org.scalatest.Ignore
@Ignore class AddSpec extends AsyncFlatSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" 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) } }When you mark a test class with a tag annotation, ScalaTest will mark each test defined in that class with that tag. Thus, marking the
AddSpec
in the above example with the@Ignore
tag annotation means that both tests in the class will be ignored. If you run the aboveAddSpec
in the Scala interpreter, you'll see:AddSpec: addSoon - should eventually compute a sum of passed Ints !!! IGNORED !!! addNow - should immediately compute a sum of passed Ints !!! IGNORED !!!
Note that marking a test class as ignored won't prevent it from being discovered by ScalaTest. Ignored classes will be discovered and run, and all their tests will be reported as ignored. This is intended to keep the ignored class visible, to encourage the developers to eventually fix and “un-ignore” it. If you want to prevent a class from being discovered at all (on the JVM, not Scala.js), use the
DoNotDiscover
annotation instead.If you want to ignore all tests of a suite on Scala.js, where annotations can't be inspected at runtime, you'll need to change
it
toignore
at each test site. To make a suite non-discoverable on Scala.js, ensure it does not declare a public no-arg constructor. You can either declare a public constructor that takes one or more arguments, or make the no-arg constructor non-public. Because this technique will also make the suite non-discoverable on the JVM, it is a good approach for suites you want to run (but not be discoverable) on both Scala.js and the JVM.Informers
One of the parameters to
AsyncFlatSpec
'srun
method is aReporter
, which will collect and report information about the running suite of tests. Information about suites and tests that were run, whether tests succeeded or failed, and tests that were ignored will be passed to theReporter
as the suite runs. Most often the reporting done by default byAsyncFlatSpec
's methods will be sufficient, but occasionally you may wish to provide custom information to theReporter
from a test. For this purpose, anInformer
that will forward information to the currentReporter
is provided via theinfo
parameterless method. You can pass the extra information to theInformer
via itsapply
method. TheInformer
will then pass the information to theReporter
via anInfoProvided
event.One use case for the
Informer
is to pass more information about a specification to the reporter. For example, theGivenWhenThen
trait provides methods that use the implicitinfo
provided byAsyncFlatSpec
to pass such information to the reporter. Here's an example:package org.scalatest.examples.asyncflatspec.info
import collection.mutable import org.scalatest._
class SetSpec extends AsyncFlatSpec with GivenWhenThen {
"A mutable Set" should "allow an element to be added" in { Given("an empty mutable Set") val set = mutable.Set.empty[String]
When("an element is added") set += "clarity"
Then("the Set should have size 1") assert(set.size === 1)
And("the Set should contain the added element") assert(set.contains("clarity"))
info("That's all folks!") succeed } }If you run this
AsyncFlatSpec
from the interpreter, you will see the following output:scala> org.scalatest.run(new SetSpec) SetSpec: A mutable Set - should allow an element to be added + Given an empty mutable Set + When an element is added + Then the Set should have size 1 + And the Set should contain the added element + That's all folks!
Documenters
AsyncFlatSpec
also provides amarkup
method that returns aDocumenter
, which allows you to send to theReporter
text formatted in Markdown syntax. You can pass the extra information to theDocumenter
via itsapply
method. TheDocumenter
will then pass the information to theReporter
via anMarkupProvided
event.Here's an example
AsyncFlatSpec
that usesmarkup
:package org.scalatest.examples.asyncflatspec.markup
import collection.mutable import org.scalatest._
class SetSpec extends AsyncFlatSpec with GivenWhenThen {
markup { """ Mutable Set ———-- A set is a collection that contains no duplicate elements. To implement a concrete mutable set, you need to provide implementations of the following methods: def contains(elem: A): Boolean def iterator: Iterator[A] def += (elem: A): this.type def -= (elem: A): this.type If you wish that methods like `take`, `drop`, `filter` return the same kind of set, you should also override: def empty: This It is also good idea to override methods `foreach` and `size` for efficiency. """ }
"A mutable Set" should "allow an element to be added" in { Given("an empty mutable Set") val set = mutable.Set.empty[String]
When("an element is added") set += "clarity"
Then("the Set should have size 1") assert(set.size === 1)
And("the Set should contain the added element") assert(set.contains("clarity"))
markup("This test finished with a **bold** statement!") succeed } }Although all of ScalaTest's built-in reporters will display the markup text in some form, the HTML reporter will format the markup information into HTML. Thus, the main purpose of
markup
is to add nicely formatted text to HTML reports. Here's what the aboveSetSpec
would look like in the HTML reporter:Notifiers and alerters
ScalaTest records text passed to
info
andmarkup
during tests, and sends the recorded text in therecordedEvents
field of test completion events likeTestSucceeded
andTestFailed
. This allows string reporters (like the standard out reporter) to showinfo
andmarkup
text after the test name in a color determined by the outcome of the test. For example, if the test fails, string reporters will show theinfo
andmarkup
text in red. If a test succeeds, string reporters will show theinfo
andmarkup
text in green. While this approach helps the readability of reports, it means that you can't useinfo
to get status updates from long running tests.To get immediate (i.e., non-recorded) notifications from tests, you can use
note
(aNotifier
) andalert
(anAlerter
). Here's an example showing the differences:package org.scalatest.examples.asyncflatspec.note
import collection.mutable import org.scalatest._
class SetSpec extends AsyncFlatSpec {
"A mutable Set" should "allow an element to be added" in {
info("info is recorded") markup("markup is *also* recorded") note("notes are sent immediately") alert("alerts are also sent immediately")
val set = mutable.Set.empty[String] set += "clarity" assert(set.size === 1) assert(set.contains("clarity")) } }Because
note
andalert
information is sent immediately, it will appear before the test name in string reporters, and its color will be unrelated to the ultimate outcome of the test:note
text will always appear in green,alert
text will always appear in yellow. Here's an example:scala> org.scalatest.run(new SetSpec) SetSpec: A mutable Set + notes are sent immediately + alerts are also sent immediately - should allow an element to be added + info is recorded + markup is *also* recorded
Another example is slowpoke notifications. If you find a test is taking a long time to complete, but you're not sure which test, you can enable slowpoke notifications. ScalaTest will use an
Alerter
to fire an event whenever a test has been running longer than a specified amount of time.In summary, use
info
andmarkup
for text that should form part of the specification output. Usenote
andalert
to send status notifications. (Because the HTML reporter is intended to produce a readable, printable specification,info
andmarkup
text will appear in the HTML report, butnote
andalert
text will not.)Pending tests
A pending test is one that has been given a name but is not yet implemented. The purpose of pending tests is to facilitate a style of testing in which documentation of behavior is sketched out before tests are written to verify that behavior (and often, before the behavior of the system being tested is itself implemented). Such sketches form a kind of specification of what tests and functionality to implement later.
To support this style of testing, a test can be given a name that specifies one bit of behavior required by the system being tested. At the end of the test, it can call method
pending
, which will cause it to complete abruptly withTestPendingException
.Because tests in ScalaTest can be designated as pending with
TestPendingException
, both the test name and any information sent to the reporter when running the test can appear in the report of a test run. (In other words, the code of a pending test is executed just like any other test.) However, because the test completes abruptly withTestPendingException
, the test will be reported as pending, to indicate the actual test, and possibly the functionality, has not yet been implemented. Here's an example:package org.scalatest.examples.asyncflatspec.pending
import org.scalatest.AsyncFlatSpec import scala.concurrent.Future
class AddSpec extends AsyncFlatSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" should "eventually compute a sum of passed Ints" in (pending)
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) } }(Note: "
(pending)
" is the body of the test. Thus the test contains just one statement, an invocation of thepending
method, which throwsTestPendingException
.) If you run this version ofAddSpec
with:scala> org.scalatest.run(new AddSpec)
It will run both tests, but report that first test is pending. You'll see:
AddSpec: addSoon - should eventually compute a sum of passed Ints (pending) addNow - should immediately compute a sum of passed Ints
One difference between an ignored test and a pending one is that an ignored test is intended to be used during significant refactorings of the code under test, when tests break and you don't want to spend the time to fix all of them immediately. You can mark some of those broken tests as ignored temporarily, so that you can focus the red bar on just failing tests you actually want to fix immediately. Later you can go back and fix the ignored tests. In other words, by ignoring some failing tests temporarily, you can more easily notice failed tests that you actually want to fix. By contrast, a pending test is intended to be used before a test and/or the code under test is written. Pending indicates you've decided to write a test for a bit of behavior, but either you haven't written the test yet, or have only written part of it, or perhaps you've written the test but don't want to implement the behavior it tests until after you've implemented a different bit of behavior you realized you need first. Thus ignored tests are designed to facilitate refactoring of existing code whereas pending tests are designed to facilitate the creation of new code.
One other difference between ignored and pending tests is that ignored tests are implemented as a test tag that is excluded by default. Thus an ignored test is never executed. By contrast, a pending test is implemented as a test that throws
TestPendingException
(which is what calling thepending
method does). Thus the body of pending tests are executed up until they throwTestPendingException
.Tagging tests
An
AsyncFlatSpec
's tests may be classified into groups by tagging them with string names. As with any suite, when executing anAsyncFlatSpec
, groups of tests can optionally be included and/or excluded. To tag anAsyncFlatSpec
's tests, you pass objects that extend classorg.scalatest.Tag
to methods that register tests. ClassTag
takes one parameter, a string name. If you have created tag annotation interfaces as described in theTag
documentation, then you will probably want to use tag names on your test functions that match. To do so, simply pass the fully qualified names of the tag interfaces to theTag
constructor. For example, if you've defined a tag annotation interface with fully qualified name,com.mycompany.tags.DbTest
, then you could create a matching tag forAsyncFlatSpec
s like this:package org.scalatest.examples.asyncflatspec.tagging
import org.scalatest.Tag
object DbTest extends Tag("com.mycompany.tags.DbTest")Given these definitions, you could place
AsyncFlatSpec
tests into groups with tags like this:import org.scalatest.AsyncFlatSpec import org.scalatest.tagobjects.Slow import scala.concurrent.Future
class AddSpec extends AsyncFlatSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" should "eventually compute a sum of passed Ints" taggedAs(Slow) 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" taggedAs(Slow, DbTest) in { val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } }This code marks both tests with the
org.scalatest.tags.Slow
tag, and the second test with thecom.mycompany.tags.DbTest
tag.The
run
method takes aFilter
, whose constructor takes an optionalSet[String]
calledtagsToInclude
and aSet[String]
calledtagsToExclude
. IftagsToInclude
isNone
, all tests will be run except those those belonging to tags listed in thetagsToExclude
Set
. IftagsToInclude
is defined, only tests belonging to tags mentioned in thetagsToInclude
set, and not mentioned intagsToExclude
, will be run.It is recommended, though not required, that you create a corresponding tag annotation when you create a
Tag
object. A tag annotation (on the JVM, not Scala.js) allows you to tag all the tests of anAsyncFlatSpec
in one stroke by annotating the class. For more information and examples, see the documentation for classTag
. On Scala.js, to tag all tests of a suite, you'll need to tag each test individually at the test site.Shared fixtures
A test fixture is composed of the objects and other artifacts (files, sockets, database connections, etc.) tests use to do their work. When multiple tests need to work with the same fixtures, it is important to try and avoid duplicating the fixture code across those tests. The more code duplication you have in your tests, the greater drag the tests will have on refactoring the actual production code.
ScalaTest recommends three techniques to eliminate such code duplication in async styles:
- Refactor using Scala
- Override
withFixture
- Mix in a before-and-after trait
Each technique is geared towards helping you reduce code duplication without introducing instance
var
s, shared mutable objects, or other dependencies between tests. Eliminating shared mutable state across tests will make your test code easier to reason about and eliminate the need to synchronize access to shared mutable state on the JVM.The following sections describe these techniques, including explaining the recommended usage for each. But first, here's a table summarizing the options:
Refactor using Scala when different tests need different fixtures. get-fixture methods The extract method refactor helps you create a fresh instances of mutable fixture objects in each test that needs them, but doesn't help you clean them up when you're done. loan-fixture methods Factor out dupicate code with the loan pattern when different tests need different fixtures that must be cleaned up afterwards. Override withFixture
when most or all tests need the same fixture.withFixture(NoArgAsyncTest)
The recommended default approach when most or all tests need the same fixture treatment. This general technique allows you, for example, to perform side effects at the beginning and end of all or most tests, transform the outcome of tests, retry tests, make decisions based on test names, tags, or other test data. Use this technique unless: - Different tests need different fixtures (refactor using Scala instead)
- An exception in fixture code should abort the suite, not fail the test (use a before-and-after trait instead)
- You have objects to pass into tests (override
withFixture(OneArgAsyncTest)
instead)
withFixture(OneArgAsyncTest)
Use when you want to pass the same fixture object or objects as a parameter into all or most tests. Mix in a before-and-after trait when you want an aborted suite, not a failed test, if the fixture code fails. BeforeAndAfter
Use this boilerplate-buster when you need to perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. BeforeAndAfterEach
Use when you want to stack traits that perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. Calling get-fixture methods
If you need to create the same mutable fixture objects in multiple tests, and don't need to clean them up after using them, the simplest approach is to write one or more get-fixture methods. A get-fixture method returns a new instance of a needed fixture object (or a holder object containing multiple fixture objects) each time it is called. You can call a get-fixture method at the beginning of each test that needs the fixture, storing the returned object or objects in local variables. Here's an example:
package org.scalatest.examples.asyncflatspec.getfixture
import org.scalatest.AsyncFlatSpec import scala.concurrent.Future
class ExampleSpec extends AsyncFlatSpec {
def fixture: Future[String] = Future { "ScalaTest is " }
"Testing" should "be easy" in { val future = fixture val result = future map { s => s + "easy!" } result map { s => assert(s == "ScalaTest is easy!") } }
it should "be fun" in { val future = fixture val result = future map { s => s + "fun!" } result map { s => assert(s == "ScalaTest is fun!") } } }If you need to configure fixture objects differently in different tests, you can pass configuration into the get-fixture method. For example, you could pass in an initial value for a fixture object as a parameter to the get-fixture method.
Overriding
withFixture(NoArgAsyncTest)
Although the get-fixture method approach takes care of setting up a fixture at the beginning of each test, it doesn't address the problem of cleaning up a fixture at the end of the test. If you just need to perform a side-effect at the beginning or end of a test, and don't need to actually pass any fixture objects into the test, you can override
withFixture(NoArgAsyncTest)
, a method defined in traitAsyncTestSuite
, a supertrait ofAsyncFlatSpec
.Trait
AsyncFlatSpec
'srunTest
method passes a no-arg async test function towithFixture(NoArgAsyncTest)
. It iswithFixture
's responsibility to invoke that test function. The default implementation ofwithFixture
simply invokes the function and returns the result, like this:// 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 thecomplete
-lastly
syntax, defined in supertraitCompleteLastly
. Thecomplete
-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 thesuper
implementation ofwithFixture
, 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 ofFuture
's registration methods:onComplete
,onSuccess
, oronFailure
. Note that if a test fails, that will be treated as ascala.util.Success(org.scalatest.Failed)
. So if you want to perform an action if a test fails, for example, you'd register the callback usingonSuccess
.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
andworld.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 towithFixture
, in addition to anapply
method that executes the test, also includes the test name and the config map passed torunTest
. Thus you can also use the test name and configuration objects in yourwithFixture
implementation.Lastly, if you want to transform the outcome in some way in
withFixture
, you'll need to use either themap
ortransform
methods ofFuture
, 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
'sapply
method will return ascala.util.Failure
only if the test completes abruptly with a "test-fatal" exception (such asOutOfMemoryError
) that should cause the suite to abort rather than the test to fail. Thus usually you would usemap
to transform future outcomes, nottransform
, so that such test-fatal exceptions pass through unchanged. The suite will abort asynchronously with any exception returned fromNoArgAsyncTest
's apply method in ascala.util.Failure
.Calling loan-fixture methods
If you need to both pass a fixture object into a test and perform cleanup at the end of the test, you'll need to use the loan pattern. If different tests need different fixtures that require cleanup, you can implement the loan pattern directly by writing loan-fixture methods. A loan-fixture method takes a function whose body forms part or all of a test's code. It creates a fixture, passes it to the test code by invoking the function, then cleans up the fixture after the function returns.
The following example shows three tests that use two fixtures, a database and a file. Both require cleanup after, so each is provided via a loan-fixture method. (In this example, the database is simulated with a
StringBuffer
.)package org.scalatest.examples.asyncflatspec.loanfixture
import java.util.concurrent.ConcurrentHashMap
import scala.concurrent.Future import scala.concurrent.ExecutionContext
object DbServer { // Simulating a database server type Db = StringBuffer private final val databases = new ConcurrentHashMap[String, Db] def createDb(name: String): Db = { val db = new StringBuffer // java.lang.StringBuffer is thread-safe databases.put(name, db) db } def removeDb(name: String): Unit = { databases.remove(name) } }
// 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 } } }
import org.scalatest._ import DbServer._ import java.util.UUID.randomUUID
class ExampleSpec extends AsyncFlatSpec {
def withDatabase(testCode: Future[Db] => Future[Assertion]) = { val dbName = randomUUID.toString // generate a unique db name val futureDb = Future { createDb(dbName) } // create the fixture complete { val futurePopulatedDb = futureDb map { db => db.append("ScalaTest is ") // perform setup } testCode(futurePopulatedDb) // "loan" the fixture to the test code } lastly { removeDb(dbName) // ensure the fixture will be cleaned up } }
def withActor(testCode: StringActor => Future[Assertion]) = { val actor = new StringActor complete { actor ! Append("ScalaTest is ") // set up the fixture testCode(actor) // "loan" the fixture to the test code } lastly { actor ! Clear // ensure the fixture will be cleaned up } }
// This test needs the actor fixture "Testing" should "be productive" in { withActor { actor => actor ! Append("productive!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is productive!") } } }
// This test needs the database fixture "Test code" should "be readable" in { withDatabase { futureDb => futureDb map { db => db.append("readable!") assert(db.toString == "ScalaTest is readable!") } } }
// This test needs both the actor and the database it should "be clear and concise" in { withDatabase { futureDb => withActor { actor => // loan-fixture methods compose actor ! Append("concise!") val futureString = actor ? GetValue val futurePair: Future[(Db, String)] = futureDb zip futureString futurePair map { case (db, s) => db.append("clear!") assert(db.toString == "ScalaTest is clear!") assert(s == "ScalaTest is concise!") } } } } }As demonstrated by the last test, loan-fixture methods compose. Not only do loan-fixture methods allow you to give each test the fixture it needs, they allow you to give a test multiple fixtures and clean everything up afterwards.
Also demonstrated in this example is the technique of giving each test its own "fixture sandbox" to play in. When your fixtures involve external side-effects, like creating databases, it is a good idea to give each database a unique name as is done in this example. This keeps tests completely isolated, allowing you to run them in parallel if desired.
Overriding
withFixture(OneArgTest)
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 overridingwithFixture(OneArgAsyncTest)
. Each test in afixture.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 specifyingFixtureParam
, and implement awithFixture
method that takes aOneArgAsyncTest
. ThiswithFixture
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 letwithFixture(NoArgAsyncTest)
invoke the test function instead of invoking the test function directly. To do so, you'll need to convert theOneArgAsyncTest
to aNoArgAsyncTest
. You can do that by passing the fixture object to thetoNoArgAsyncTest
method ofOneArgAsyncTest
. In other words, instead of writing “test(theFixture)
”, you'd delegate responsibility for invoking the test function to thewithFixture(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!") } } }In this example, the tests required one fixture object, a
StringActor
. If your tests need multiple fixture objects, you can simply define theFixtureParam
type to be a tuple containing the objects or, alternatively, a case class containing the objects. For more information on thewithFixture(OneArgAsyncTest)
technique, see the documentation forfixture.AsyncFlatSpec
.Mixing in
BeforeAndAfter
In all the shared fixture examples shown so far, the activities of creating, setting up, and cleaning up the fixture objects have been performed during the test. This means that if an exception occurs during any of these activities, it will be reported as a test failure. Sometimes, however, you may want setup to happen before the test starts, and cleanup after the test has completed, so that if an exception occurs during setup or cleanup, the entire suite aborts and no more tests are attempted. The simplest way to accomplish this in ScalaTest is to mix in trait
BeforeAndAfter
. With this trait you can denote a bit of code to run before each test withbefore
and/or after each test each test withafter
, like this:package org.scalatest.examples.asyncflatspec.beforeandafter
import org.scalatest.AsyncFlatSpec import org.scalatest.BeforeAndAfter 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 AsyncFlatSpec with BeforeAndAfter {
final val actor = new StringActor
before { actor ! Append("ScalaTest is ") // set up the fixture }
after { actor ! Clear // clean up the fixture }
"Testing" should "be easy" in { actor ! Append("easy!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is easy!") } }
it should "be fun" in { actor ! Append("fun!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is fun!") } } }Note that the only way
before
andafter
code can communicate with test code is via some side-effecting mechanism, commonly by reassigning instancevar
s or by changing the state of mutable objects held from instanceval
s (as in this example). If using instancevar
s or mutable objects held from instanceval
s you wouldn't be able to run tests in parallel in the same instance of the test class (on the JVM, not Scala.js) unless you synchronized access to the shared, mutable state.Note that on the JVM, if you override ScalaTest's default serial execution context, you will likely need to worry about synchronizing access to shared mutable fixture state, because the execution context may assign different threads to process different
Future
transformations. Although access to mutable state along the same linear chain ofFuture
transformations need not be synchronized, it can be difficult to spot cases where these constraints are violated. The best approach is to use only immutable objects when transformingFuture
s. When that's not practical, involve only thread-safe mutable objects, as is done in the above example. On Scala.js, by contrast, you need not worry about thread synchronization, because in effect only one thread exists.Although
BeforeAndAfter
provides a minimal-boilerplate way to execute code before and after tests, it isn't designed to enable stackable traits, because the order of execution would be non-obvious. If you want to factor out before and after code that is common to multiple test suites, you should use traitBeforeAndAfterEach
instead, as shown later in the next section, composing fixtures by stacking traits.Composing fixtures by stacking traits
In larger projects, teams often end up with several different fixtures that test classes need in different combinations, and possibly initialized (and cleaned up) in different orders. A good way to accomplish this in ScalaTest is to factor the individual fixtures into traits that can be composed using the stackable trait pattern. This can be done, for example, by placing
withFixture
methods in several traits, each of which callsuper.withFixture
. Here's an example in which theStringBuilderActor
andStringBufferActor
fixtures used in the previous examples have been factored out into two stackable fixture traits namedBuilder
andBuffer
:package org.scalatest.examples.asyncflatspec.composingwithasyncfixture
import org.scalatest._ import org.scalatest.SuiteMixin import collection.mutable.ListBuffer 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 StringBuilderActor { // 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 StringBufferActor { private final val buf = ListBuffer.empty[String] def !(op: StringOp): Unit = synchronized { op match { case Append(value) => buf += value case Clear => buf.clear() } } def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[List[String]] = Future { synchronized { buf.toList } } }
trait Builder extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val builderActor = new StringBuilderActor
abstract override def withFixture(test: NoArgAsyncTest) = { builderActor ! Append("ScalaTest is ") complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { builderActor ! Clear } } }
trait Buffer extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val bufferActor = new StringBufferActor
abstract override def withFixture(test: NoArgAsyncTest) = { complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { bufferActor ! Clear } } }
class ExampleSpec extends AsyncFlatSpec with Builder with Buffer {
"Testing" should "be easy" in { builderActor ! Append("easy!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is easy!") assert(lst.isEmpty) bufferActor ! Append("sweet") succeed } }
it should "be fun" in { builderActor ! Append("fun!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is fun!") assert(lst.isEmpty) bufferActor ! Append("awesome") succeed } } }By mixing in both the
Builder
andBuffer
traits,ExampleSpec
gets both fixtures, which will be initialized before each test and cleaned up after. The order the traits are mixed together determines the order of execution. In this case,Builder
is “super” toBuffer
. If you wantedBuffer
to be “super” toBuilder
, you need only switch the order you mix them together, like this:class Example2Spec extends AsyncFlatSpec with Buffer with Builder
If you only need one fixture you mix in only that trait:
class Example3Spec extends AsyncFlatSpec with Builder
Another way to create stackable fixture traits is by extending the
BeforeAndAfterEach
and/orBeforeAndAfterAll
traits.BeforeAndAfterEach
has abeforeEach
method that will be run before each test (like JUnit'ssetUp
), and anafterEach
method that will be run after (like JUnit'stearDown
). Similarly,BeforeAndAfterAll
has abeforeAll
method that will be run before all tests, and anafterAll
method that will be run after all tests. Here's what the previously shown example would look like if it were rewritten to use theBeforeAndAfterEach
methods instead ofwithFixture
:package org.scalatest.examples.asyncflatspec.composingbeforeandaftereach
import org.scalatest._ import org.scalatest.BeforeAndAfterEach import collection.mutable.ListBuffer 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 StringBuilderActor { // 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 StringBufferActor { private final val buf = ListBuffer.empty[String] def !(op: StringOp): Unit = synchronized { op match { case Append(value) => buf += value case Clear => buf.clear() } } def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[List[String]] = Future { synchronized { buf.toList } } }
trait Builder extends BeforeAndAfterEach { this: Suite =>
final val builderActor = new StringBuilderActor
override def beforeEach() { builderActor ! Append("ScalaTest is ") super.beforeEach() // To be stackable, must call super.beforeEach }
override def afterEach() { try super.afterEach() // To be stackable, must call super.afterEach finally builderActor ! Clear } }
trait Buffer extends BeforeAndAfterEach { this: Suite =>
final val bufferActor = new StringBufferActor
override def afterEach() { try super.afterEach() // To be stackable, must call super.afterEach finally bufferActor ! Clear } }
class ExampleSpec extends AsyncFlatSpec with Builder with Buffer {
"Testing" should "be easy" in { builderActor ! Append("easy!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is easy!") assert(lst.isEmpty) bufferActor ! Append("sweet") succeed } }
it should "be fun" in { builderActor ! Append("fun!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is fun!") assert(lst.isEmpty) bufferActor ! Append("awesome") succeed } } }To get the same ordering as
withFixture
, place yoursuper.beforeEach
call at the end of eachbeforeEach
method, and thesuper.afterEach
call at the beginning of eachafterEach
method, as shown in the previous example. It is a good idea to invokesuper.afterEach
in atry
block and perform cleanup in afinally
clause, as shown in the previous example, because this ensures the cleanup code is performed even ifsuper.afterEach
throws an exception.The difference between stacking traits that extend
BeforeAndAfterEach
versus traits that implementwithFixture
is that setup and cleanup code happens before and after the test inBeforeAndAfterEach
, but at the beginning and end of the test inwithFixture
. Thus if awithFixture
method completes abruptly with an exception, it is considered a failed test. By contrast, if any of thebeforeEach
orafterEach
methods ofBeforeAndAfterEach
complete abruptly, it is considered an aborted suite, which will result in aSuiteAborted
event.Shared tests
Sometimes you may want to run the same test code on different fixture objects. In other words, you may want to write tests that are "shared" by different fixture objects. To accomplish this in an
AsyncFlatSpec
, you first place shared tests in behavior functions. These behavior functions will be invoked during the construction phase of anyAsyncFlatSpec
that uses them, so that the tests they contain will be registered as tests in thatAsyncFlatSpec
. For example, given thisStackActor
class:package org.scalatest.examples.asyncflatspec.sharedtests
import scala.collection.mutable.ListBuffer import scala.concurrent.Future import scala.concurrent.ExecutionContext
// Stack operations case class Push[T](value: T) sealed abstract class StackOp case object Pop extends StackOp case object Peek extends StackOp case object Size extends StackOp
// Stack info case class StackInfo[T](top: Option[T], size: Int, max: Int) { require(size > 0, "size was less than zero") require(max > size, "max was less than size") val isFull: Boolean = size == max val isEmpty: Boolean = size == 0 }
class StackActor[T](Max: Int, name: String) {
private final val buf = new ListBuffer[T]
def !(push: Push[T]): Unit = synchronized { if (buf.size != Max) buf.prepend(push.value) else throw new IllegalStateException("can't push onto a full stack") }
def ?(op: StackOp)(implicit c: ExecutionContext): Future[StackInfo[T]] = synchronized { op match { case Pop => Future { if (buf.size != 0) StackInfo(Some(buf.remove(0)), buf.size, Max) else throw new IllegalStateException("can't pop an empty stack") } case Peek => Future { if (buf.size != 0) StackInfo(Some(buf(0)), buf.size, Max) else throw new IllegalStateException("can't peek an empty stack") } case Size => Future { StackInfo(None, buf.size, Max) } } }
override def toString: String = name }You may want to test the stack represented by the
StackActor
class in different states: empty, full, with one item, with one item less than capacity, etc. You may find you have several tests that make sense any time the stack is non-empty. Thus you'd ideally want to run those same tests for three stack fixture objects: a full stack, a stack with a one item, and a stack with one item less than capacity. With shared tests, you can factor these tests out into a behavior function, into which you pass the stack fixture to use when running the tests. So in yourAsyncFlatSpec
forStackActor
, you'd invoke the behavior function three times, passing in each of the three stack fixtures so that the shared tests are run for all three fixtures.You can define a behavior function that encapsulates these shared tests inside the
AsyncFlatSpec
that uses them. If they are shared between differentAsyncFlatSpec
s, however, you could also define them in a separate trait that is mixed into eachAsyncFlatSpec
that uses them. For example, here thenonEmptyStackActor
behavior function (in this case, a behavior method) is defined in a trait along with another method containing shared tests for non-full stacks:import org.scalatest.AsyncFlatSpec
trait AsyncFlatSpecStackBehaviors { this: AsyncFlatSpec =>
def nonEmptyStackActor(createNonEmptyStackActor: => StackActor[Int], lastItemAdded: Int, name: String): Unit = {
it should ("return non-empty StackInfo when Size is fired at non-empty stack actor: " + name) in { val stackActor = createNonEmptyStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(!stackInfo.isEmpty) } }
it should ("return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: " + name) in { val stackActor = createNonEmptyStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePeek <- stackActor ? Size afterPeek <- stackActor ? Peek } yield (beforePeek, afterPeek) futurePair map { case (beforePeek, afterPeek) => assert(afterPeek.top == Some(lastItemAdded)) assert(afterPeek.size == beforePeek.size) } }
it should ("return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: " + name) in { val stackActor = createNonEmptyStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePop <- stackActor ? Size afterPop <- stackActor ? Pop } yield (beforePop, afterPop) futurePair map { case (beforePop, afterPop) => assert(afterPop.top == Some(lastItemAdded)) assert(afterPop.size == beforePop.size - 1) } } }
def nonFullStackActor(createNonFullStackActor: => StackActor[Int], name: String): Unit = {
it should ("return non-full StackInfo when Size is fired at non-full stack actor: " + name) in { val stackActor = createNonFullStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(!stackInfo.isFull) } }
it should ("return before and after StackInfo that has existing size + 1 and new item as top when Push is fired at non-full stack actor: " + name) in { val stackActor = createNonFullStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePush <- stackActor ? Size afterPush <- { stackActor ! Push(7); stackActor ? Peek } } yield (beforePush, afterPush) futurePair map { case (beforePush, afterPush) => assert(afterPush.top == Some(7)) assert(afterPush.size == beforePush.size + 1) } } } }Given these behavior functions, you could invoke them directly, but
AsyncFlatSpec
offers a DSL for the purpose, which looks like this:it should behave like nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName) it should behave like nonFullStackActor(almostEmptyStackActor, almostEmptyStackActorName)
Here's an example:
class StackSpec extends AsyncFlatSpec with AsyncFlatSpecStackBehaviors {
val Max = 10 val LastValuePushed = Max - 1
// Stack fixture creation methods val emptyStackActorName = "empty stack actor" def emptyStackActor = new StackActor[Int](Max, emptyStackActorName )
val fullStackActorName = "full stack actor" def fullStackActor = { val stackActor = new StackActor[Int](Max, fullStackActorName ) for (i <- 0 until Max) stackActor ! Push(i) stackActor }
val almostEmptyStackActorName = "almost empty stack actor" def almostEmptyStackActor = { val stackActor = new StackActor[Int](Max, almostEmptyStackActorName ) stackActor ! Push(LastValuePushed) stackActor }
val almostFullStackActorName = "almost full stack actor" def almostFullStackActor = { val stackActor = new StackActor[Int](Max, almostFullStackActorName) for (i <- 1 to LastValuePushed) stackActor ! Push(i) stackActor }
"A Stack actor (when empty)" should "return empty StackInfo when Size is fired at it" in { val stackActor = emptyStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(stackInfo.isEmpty) } }
it should "complain when Peek is fired at it" in { recoverToSucceededIf[IllegalStateException] { emptyStackActor ? Peek } }
it should "complain when Pop is fired at it" in { recoverToSucceededIf[IllegalStateException] { emptyStackActor ? Pop } }
"A Stack actor (when non-empty)" should behave like nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName)
it should behave like nonFullStackActor(almostEmptyStackActor, almostEmptyStackActorName)
it should behave like nonEmptyStackActor(almostFullStackActor, LastValuePushed, almostFullStackActorName)
it should behave like nonFullStackActor(almostFullStackActor, almostFullStackActorName)
"A Stack actor (when full)" should "return full StackInfo when Size is fired at it" in { val stackActor = fullStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(stackInfo.isFull) } }
it should behave like nonEmptyStackActor(fullStackActor, LastValuePushed, fullStackActorName)
it should "complain when Push is fired at it" in { val stackActor = fullStackActor assertThrows[IllegalStateException] { stackActor ! Push(10) } } }If you load these classes into the Scala interpreter (with scalatest's JAR file on the class path), and execute it, you'll see:
scala> org.scalatest.run(new StackSpec) StackSpec: A Stack actor (when empty) - should return empty StackInfo when Size is fired at it - should complain when Peek is fired at it - should complain when Pop is fired at it A Stack actor (when non-empty) - should return non-empty StackInfo when Size is fired at non-empty stack actor: almost empty stack actor - should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost empty stack actor - should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost empty stack actor - should return non-full StackInfo when Size is fired at non-full stack actor: almost empty stack actor - should return before and after StackInfo that has existing size + 1 and new item as top when Push is fired at non-full stack actor: almost empty stack actor - should return non-empty StackInfo when Size is fired at non-empty stack actor: almost full stack actor - should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost full stack actor - should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost full stack actor - should return non-full StackInfo when Size is fired at non-full stack actor: almost full stack actor - should return before and after StackInfo that has existing size + 1 and new item as top when Push is fired at non-full stack actor: almost full stack actor A Stack actor (when full) - should return full StackInfo when Size is fired at it - should return non-empty StackInfo when Size is fired at non-empty stack actor: full stack actor - should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: full stack actor - should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: full stack actor - should complain when Push is fired at it
One thing to keep in mind when using shared tests is that in ScalaTest, each test in a suite must have a unique name. If you register the same tests repeatedly in the same suite, one problem you may encounter is an exception at runtime complaining that multiple tests are being registered with the same test name. Although in an
AsyncFlatSpec
, thebehavior of
clause is a nesting construct analogous toAsyncFunSpec
'sdescribe
clause, you many sometimes need to do a bit of extra work to ensure that the test names are unique. If a duplicate test name problem shows up in anAsyncFlatSpec
, you'll need to pass in a prefix or suffix string to add to each test name. You can calltoString
on the shared fixture object, or pass this string the same way you pass any other data needed by the shared tests. This is the approach taken by the previousAsyncFlatSpecStackBehaviors
example.Given this
AsyncFlatSpecStackBehaviors
trait, calling it with thealmostEmptyStackActor
fixture, like this:"A Stack actor (when non-empty)" should behave like nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName)
yields test names:
A Stack actor (when non-empty) should return non-empty StackInfo when Size is fired at non-empty stack actor: almost empty stack actor
A Stack actor (when non-empty) should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost empty stack actor
A Stack actor (when non-empty) should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost empty stack actor
Whereas calling it with the
almostFullStackActor
fixture, like this:it should behave like nonEmptyStackActor(almostFullStackActor, LastValuePushed, almostFullStackActorName)
yields different test names:
A Stack actor (when non-empty) should return non-empty StackInfo when Size is fired at non-empty stack actor: almost full stack actor
A Stack actor (when non-empty) should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost full stack actor
A Stack actor (when non-empty) should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost full stack actor
- trait AsyncFlatSpecLike extends AsyncTestSuite with AsyncTestRegistration with ShouldVerb with MustVerb with CanVerb with Informing with Notifying with Alerting with Documenting
Implementation trait for class
AsyncFlatSpec
, which facilitates a “behavior-driven” style of development (BDD), in which tests are combined with text that specifies the behavior the tests verify.Implementation trait for class
AsyncFlatSpec
, which facilitates a “behavior-driven” style of development (BDD), in which tests are combined with text that specifies the behavior the tests verify.AsyncFlatSpec
is a class, not a trait, to minimize compile time given there is a slight compiler overhead to mixing in traits compared to extending classes. If you need to mix the behavior ofAsyncFlatSpec
into some other class, you can use this trait instead, because classAsyncFlatSpec
does nothing more than extend this trait and add a nicetoString
implementation.See the documentation of the class for a detailed overview of
AsyncFlatSpec
. - abstract class AsyncFreeSpec extends AsyncFreeSpecLike
Enables testing of asynchronous code without blocking, using a style consistent with traditional
FreeSpec
tests.Enables testing of asynchronous code without blocking, using a style consistent with traditional
FreeSpec
tests.Recommended Usage: AsyncFreeSpec
is intended to enable users ofFreeSpec
to write non-blocking asynchronous tests that are consistent with their traditionalFreeSpec
tests. Note:AsyncFreeSpec
is intended for use in special situations where non-blocking asynchronous testing is needed, with classFreeSpec
used for general needs.Given a
Future
returned by the code you are testing, you need not block until theFuture
completes before performing assertions against its value. You can instead map those assertions onto theFuture
and return the resultingFuture[Assertion]
to ScalaTest. The test will complete asynchronously, when theFuture[Assertion]
completes.Here's an example
AsyncFreeSpec
:package org.scalatest.examples.asyncfreespec
import org.scalatest.AsyncFreeSpec import scala.concurrent.Future
class AddSpec extends AsyncFreeSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" - { "will 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" - { "will 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) } } }In an
AsyncFreeSpec
you write a test with a string followed byin
and the body of the test in curly braces, like this:"will eventually compute a sum of passed Ints" in { // ... }
You can nest a test inside any number of description clauses, which you write with a string followed by a dash character and a block, like this:
"addSoon" - { // ... }
You can nest description clauses as deeply as you want. Because the description clause is denoted with an operator, not a word like
should
, you are free to structure the text however you wish. In short, you structure anAsyncFreeSpec
exactly like aFreeSpec
, but with tests having result typeAssertion
orFuture[Assertion]
. For more examples of structure, see the documentation forFreeSpec
.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, isFuture[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 toFuture[Assertion]
and registered. The implicit conversion is fromAssertion
toFuture[Assertion]
, so you must end synchronous tests in some ScalaTest assertion or matcher expression. If a test would not otherwise end in typeAssertion
, you can placesucceed
at the end of the test.succeed
, a field in traitAssertions
, returns theSucceeded
singleton:scala> succeed res2: org.scalatest.Assertion = Succeeded
Thus placing
succeed
at the end of a test body will satisfy the type checker:"will immediately compute a sum of passed Ints" - { val sum: Int = addNow(1, 2) assert(sum == 3) println("hi") // println has result type Unit succeed // succeed has result type Assertion }
An
AsyncFreeSpec
's lifecycle has two phases: the registration phase and the ready phase. It starts in registration phase and enters ready phase the first timerun
is called on it. It then remains in ready phase for the remainder of its lifetime.Tests can only be registered with the
it
method while theAsyncFreeSpec
is in its registration phase. Any attempt to register a test after theAsyncFreeSpec
has entered its ready phase, i.e., afterrun
has been invoked on theAsyncFreeSpec
, will be met with a thrownTestRegistrationClosedException
. The recommended style of usingAsyncFreeSpec
is to register tests during object construction as is done in all the examples shown here. If you keep to the recommended style, you should never see aTestRegistrationClosedException
.Asynchronous execution model
AsyncFreeSpec
extendsAsyncTestSuite
, which provides an implicitscala.concurrent.ExecutionContext
namedexecutionContext
. This execution context is used byAsyncFreeSpec
to transform theFuture[Assertion]
s returned by each test into theFutureOutcome
returned by thetest
function passed towithFixture
. ThisExecutionContext
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 theFutureOutcome
returned from thetest
function. Other tasks that will be queued are any transformations of, or callbacks registered on,Future
s that occur in your test body, including any assertions you map ontoFuture
s. 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 ofFuture
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]
. ThisFuture[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 theFuture[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
FreeSpec
withScalaFutures
instead. Alternatively, you could override theexecutionContext
and use a traditionalExecutionContext
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 therunNow
execution context on Scala.js instead of the defaultqueue
, you would write:// on Scala.js implicit override def executionContext = org.scalatest.concurrent.TestExecutionContext.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 anAsyncFreeSpec
will be executed one after another, i.e., serially. This is true whether those tests returnAssertion
orFuture[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
AsyncFreeSpec
to be executed in parallel, you must mix inParallelTestExecution
and enable parallel execution of tests in your build. You enable parallel execution inRunner
with the-P
command line flag. In the ScalaTest Maven Plugin, setparallel
totrue
. Insbt
, 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 theDistributor
, and allowed to complete in parallel, using threads from theexecutionContext
. If you are using ScalaTest's serial execution context, the JVM default, asynchronous tests will run in parallel very much like traditional (such asFreeSpec
) tests run in parallel: 1) BecauseParallelTestExecution
extendsOneInstancePerTest
, 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 ofFuture
s 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 invokedrun
, 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 theexecutionContext
. 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 inParallelTestExecution
is executed: the tests will run sequentially. If you use an execution context backed by a thread-pool, such asglobal
, 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
andrecoverToExceptionIf
methods of traitRecoverMethods
. Because this trait is mixed into supertraitAsyncTestSuite
, both of these methods are available by default in anAsyncFreeSpec
.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 toassertThrows
, except in the context of a future. It transforms aFuture
of any type into aFuture[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 therecoverToSucceededIf
in its behavior when the assertion succeeds:recoverToSucceededIf
yields aFuture[Assertion]
, whereasrecoverToExceptionIf
yields aFuture[T]
, whereT
is the expected exception type.recoverToExceptionIf[IllegalStateException] { // Result type: Future[IllegalStateException] emptyStackActor ? Peek }
In other words,
recoverToExpectionIf
is tointercept
asrecovertToSucceededIf
is toassertThrows
. 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 showingrecoverToExceptionIf
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]"))
Ignored tests
To support the common use case of temporarily disabling a test, with the good intention of resurrecting the test at a later time,
AsyncFreeSpec
adds a methodignore
to strings that can be used instead ofin
to register a test. For example, to temporarily disable the test with the name"addSoon will eventually compute a sum of passed Ints"
, just change “in
” into “ignore
,” like this:package org.scalatest.examples.asyncfreespec.ignore
import org.scalatest.AsyncFreeSpec import scala.concurrent.Future
class AddSpec extends AsyncFreeSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" - { "will eventually compute a sum of passed Ints" ignore { 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" - { "will 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) } } }If you run this version of
AddSpec
with:scala> org.scalatest.run(new AddSpec)
It will run only the second test and report that the first test was ignored:
AddSpec: addSoon - will eventually compute a sum of passed Ints !!! IGNORED !!! addNow - will immediately compute a sum of passed Ints
If you wish to temporarily ignore an entire suite of tests, you can (on the JVM, not Scala.js) annotate the test class with
@Ignore
, like this:package org.scalatest.examples.asyncfreespec.ignoreall
import org.scalatest.AsyncFreeSpec import scala.concurrent.Future import org.scalatest.Ignore
@Ignore class AddSpec extends AsyncFreeSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" - { "will 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" - { "will 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) } } }When you mark a test class with a tag annotation, ScalaTest will mark each test defined in that class with that tag. Thus, marking the
AddSpec
in the above example with the@Ignore
tag annotation means that both tests in the class will be ignored. If you run the aboveAddSpec
in the Scala interpreter, you'll see:AddSpec: addSoon - will eventually compute a sum of passed Ints !!! IGNORED !!! addNow - will immediately compute a sum of passed Ints !!! IGNORED !!!
Note that marking a test class as ignored won't prevent it from being discovered by ScalaTest. Ignored classes will be discovered and run, and all their tests will be reported as ignored. This is intended to keep the ignored class visible, to encourage the developers to eventually fix and “un-ignore” it. If you want to prevent a class from being discovered at all (on the JVM, not Scala.js), use the
DoNotDiscover
annotation instead.If you want to ignore all tests of a suite on Scala.js, where annotations can't be inspected at runtime, you'll need to change
it
toignore
at each test site. To make a suite non-discoverable on Scala.js, ensure it does not declare a public no-arg constructor. You can either declare a public constructor that takes one or more arguments, or make the no-arg constructor non-public. Because this technique will also make the suite non-discoverable on the JVM, it is a good approach for suites you want to run (but not be discoverable) on both Scala.js and the JVM.Informers
One of the parameters to
AsyncFreeSpec
'srun
method is aReporter
, which will collect and report information about the running suite of tests. Information about suites and tests that were run, whether tests succeeded or failed, and tests that were ignored will be passed to theReporter
as the suite runs. Most often the reporting done by default byAsyncFreeSpec
's methods will be sufficient, but occasionally you may wish to provide custom information to theReporter
from a test. For this purpose, anInformer
that will forward information to the currentReporter
is provided via theinfo
parameterless method. You can pass the extra information to theInformer
via itsapply
method. TheInformer
will then pass the information to theReporter
via anInfoProvided
event.One use case for the
Informer
is to pass more information about a specification to the reporter. For example, theGivenWhenThen
trait provides methods that use the implicitinfo
provided byAsyncFreeSpec
to pass such information to the reporter. Here's an example:package org.scalatest.examples.asyncfreespec.info
import collection.mutable import org.scalatest._
class SetSpec extends AsyncFreeSpec with GivenWhenThen {
"A mutable Set" - { "should allow an element to be added" in { Given("an empty mutable Set") val set = mutable.Set.empty[String]
When("an element is added") set += "clarity"
Then("the Set should have size 1") assert(set.size === 1)
And("the Set should contain the added element") assert(set.contains("clarity"))
info("That's all folks!") succeed } } }If you run this
AsyncFreeSpec
from the interpreter, you will see the following output:scala> org.scalatest.run(new SetSpec) A mutable Set - should allow an element to be added + Given an empty mutable Set + When an element is added + Then the Set should have size 1 + And the Set should contain the added element + That's all folks!
Documenters
AsyncFreeSpec
also provides amarkup
method that returns aDocumenter
, which allows you to send to theReporter
text formatted in Markdown syntax. You can pass the extra information to theDocumenter
via itsapply
method. TheDocumenter
will then pass the information to theReporter
via anMarkupProvided
event.Here's an example
AsyncFreeSpec
that usesmarkup
:package org.scalatest.examples.asyncfreespec.markup
import collection.mutable import org.scalatest._
class SetSpec extends AsyncFreeSpec with GivenWhenThen {
markup { """ Mutable Set ———-- A set is a collection that contains no duplicate elements. To implement a concrete mutable set, you need to provide implementations of the following methods: def contains(elem: A): Boolean def iterator: Iterator[A] def += (elem: A): this.type def -= (elem: A): this.type If you wish that methods like `take`, `drop`, `filter` return the same kind of set, you should also override: def empty: This It is also good idea to override methods `foreach` and `size` for efficiency. """ }
"A mutable Set" - { "should allow an element to be added" in { Given("an empty mutable Set") val set = mutable.Set.empty[String]
When("an element is added") set += "clarity"
Then("the Set should have size 1") assert(set.size === 1)
And("the Set should contain the added element") assert(set.contains("clarity"))
markup("This test finished with a **bold** statement!") succeed } } }Although all of ScalaTest's built-in reporters will display the markup text in some form, the HTML reporter will format the markup information into HTML. Thus, the main purpose of
markup
is to add nicely formatted text to HTML reports. Here's what the aboveSetSpec
would look like in the HTML reporter:Notifiers and alerters
ScalaTest records text passed to
info
andmarkup
during tests, and sends the recorded text in therecordedEvents
field of test completion events likeTestSucceeded
andTestFailed
. This allows string reporters (like the standard out reporter) to showinfo
andmarkup
text after the test name in a color determined by the outcome of the test. For example, if the test fails, string reporters will show theinfo
andmarkup
text in red. If a test succeeds, string reporters will show theinfo
andmarkup
text in green. While this approach helps the readability of reports, it means that you can't useinfo
to get status updates from long running tests.To get immediate (i.e., non-recorded) notifications from tests, you can use
note
(aNotifier
) andalert
(anAlerter
). Here's an example showing the differences:package org.scalatest.examples.asyncfreespec.note
import collection.mutable import org.scalatest._
class SetSpec extends AsyncFreeSpec {
"A mutable Set" - { "should allow an element to be added" in {
info("info is recorded") markup("markup is *also* recorded") note("notes are sent immediately") alert("alerts are also sent immediately")
val set = mutable.Set.empty[String] set += "clarity" assert(set.size === 1) assert(set.contains("clarity")) } } }scala> org.scalatest.run(new SetSpec) SetSpec: A mutable Set + notes are sent immediately + alerts are also sent immediately - should allow an element to be added + info is recorded + markup is *also* recorded
Another example is slowpoke notifications. If you find a test is taking a long time to complete, but you're not sure which test, you can enable slowpoke notifications. ScalaTest will use an
Alerter
to fire an event whenever a test has been running longer than a specified amount of time.In summary, use
info
andmarkup
for text that should form part of the specification output. Usenote
andalert
to send status notifications. (Because the HTML reporter is intended to produce a readable, printable specification,info
andmarkup
text will appear in the HTML report, butnote
andalert
text will not.)Pending tests
A pending test is one that has been given a name but is not yet implemented. The purpose of pending tests is to facilitate a style of testing in which documentation of behavior is sketched out before tests are written to verify that behavior (and often, before the behavior of the system being tested is itself implemented). Such sketches form a kind of specification of what tests and functionality to implement later.
To support this style of testing, a test can be given a name that specifies one bit of behavior required by the system being tested. At the end of the test, it can call method
pending
, which will cause it to complete abruptly withTestPendingException
.Because tests in ScalaTest can be designated as pending with
TestPendingException
, both the test name and any information sent to the reporter when running the test can appear in the report of a test run. (In other words, the code of a pending test is executed just like any other test.) However, because the test completes abruptly withTestPendingException
, the test will be reported as pending, to indicate the actual test, and possibly the functionality, has not yet been implemented. Here's an example:package org.scalatest.examples.asyncfreespec.pending
import org.scalatest.AsyncFreeSpec import scala.concurrent.Future
class AddSpec extends AsyncFreeSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" - { "will eventually compute a sum of passed Ints" in (pending) }
def addNow(addends: Int*): Int = addends.sum
"addNow" - { "will 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) } } }(Note: "
(pending)
" is the body of the test. Thus the test contains just one statement, an invocation of thepending
method, which throwsTestPendingException
.) If you run this version ofAddSpec
with:scala> org.scalatest.run(new AddSpec)
It will run both tests, but report that first test is pending. You'll see:
AddSpec: addSoon - will eventually compute a sum of passed Ints (pending) addNow - will immediately compute a sum of passed Ints
One difference between an ignored test and a pending one is that an ignored test is intended to be used during significant refactorings of the code under test, when tests break and you don't want to spend the time to fix all of them immediately. You can mark some of those broken tests as ignored temporarily, so that you can focus the red bar on just failing tests you actually want to fix immediately. Later you can go back and fix the ignored tests. In other words, by ignoring some failing tests temporarily, you can more easily notice failed tests that you actually want to fix. By contrast, a pending test is intended to be used before a test and/or the code under test is written. Pending indicates you've decided to write a test for a bit of behavior, but either you haven't written the test yet, or have only written part of it, or perhaps you've written the test but don't want to implement the behavior it tests until after you've implemented a different bit of behavior you realized you need first. Thus ignored tests are designed to facilitate refactoring of existing code whereas pending tests are designed to facilitate the creation of new code.
One other difference between ignored and pending tests is that ignored tests are implemented as a test tag that is excluded by default. Thus an ignored test is never executed. By contrast, a pending test is implemented as a test that throws
TestPendingException
(which is what calling thepending
method does). Thus the body of pending tests are executed up until they throwTestPendingException
.Tagging tests
An
AsyncFreeSpec
's tests may be classified into groups by tagging them with string names. As with any suite, when executing anAsyncFreeSpec
, groups of tests can optionally be included and/or excluded. To tag anAsyncFreeSpec
's tests, you pass objects that extend classorg.scalatest.Tag
to methods that register tests. ClassTag
takes one parameter, a string name. If you have created tag annotation interfaces as described in theTag
documentation, then you will probably want to use tag names on your test functions that match. To do so, simply pass the fully qualified names of the tag interfaces to theTag
constructor. For example, if you've defined a tag annotation interface with fully qualified name,com.mycompany.tags.DbTest
, then you could create a matching tag forAsyncFreeSpec
s like this:package org.scalatest.examples.asyncfreespec.tagging
import org.scalatest.Tag
object DbTest extends Tag("com.mycompany.tags.DbTest")Given these definitions, you could place
AsyncFreeSpec
tests into groups with tags like this:import org.scalatest.AsyncFreeSpec import org.scalatest.tagobjects.Slow import scala.concurrent.Future
class AddSpec extends AsyncFreeSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" - { "will eventually compute a sum of passed Ints" taggedAs(Slow) 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" - { "will immediately compute a sum of passed Ints" taggedAs(Slow, DbTest) in {
val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } } }This code marks both tests with the
org.scalatest.tags.Slow
tag, and the second test with thecom.mycompany.tags.DbTest
tag.The
run
method takes aFilter
, whose constructor takes an optionalSet[String]
calledtagsToInclude
and aSet[String]
calledtagsToExclude
. IftagsToInclude
isNone
, all tests will be run except those those belonging to tags listed in thetagsToExclude
Set
. IftagsToInclude
is defined, only tests belonging to tags mentioned in thetagsToInclude
set, and not mentioned intagsToExclude
, will be run.It is recommended, though not required, that you create a corresponding tag annotation when you create a
Tag
object. A tag annotation (on the JVM, not Scala.js) allows you to tag all the tests of anAsyncFreeSpec
in one stroke by annotating the class. For more information and examples, see the documentation for classTag
. On Scala.js, to tag all tests of a suite, you'll need to tag each test individually at the test site.Shared fixtures
A test fixture is composed of the objects and other artifacts (files, sockets, database connections, etc.) tests use to do their work. When multiple tests need to work with the same fixtures, it is important to try and avoid duplicating the fixture code across those tests. The more code duplication you have in your tests, the greater drag the tests will have on refactoring the actual production code.
ScalaTest recommends three techniques to eliminate such code duplication in async styles:
- Refactor using Scala
- Override
withFixture
- Mix in a before-and-after trait
Each technique is geared towards helping you reduce code duplication without introducing instance
var
s, shared mutable objects, or other dependencies between tests. Eliminating shared mutable state across tests will make your test code easier to reason about and eliminate the need to synchronize access to shared mutable state on the JVM.The following sections describe these techniques, including explaining the recommended usage for each. But first, here's a table summarizing the options:
Refactor using Scala when different tests need different fixtures. get-fixture methods The extract method refactor helps you create a fresh instances of mutable fixture objects in each test that needs them, but doesn't help you clean them up when you're done. loan-fixture methods Factor out dupicate code with the loan pattern when different tests need different fixtures that must be cleaned up afterwards. Override withFixture
when most or all tests need the same fixture.withFixture(NoArgAsyncTest)
The recommended default approach when most or all tests need the same fixture treatment. This general technique allows you, for example, to perform side effects at the beginning and end of all or most tests, transform the outcome of tests, retry tests, make decisions based on test names, tags, or other test data. Use this technique unless: - Different tests need different fixtures (refactor using Scala instead)
- An exception in fixture code should abort the suite, not fail the test (use a before-and-after trait instead)
- You have objects to pass into tests (override
withFixture(OneArgAsyncTest)
instead)
withFixture(OneArgAsyncTest)
Use when you want to pass the same fixture object or objects as a parameter into all or most tests. Mix in a before-and-after trait when you want an aborted suite, not a failed test, if the fixture code fails. BeforeAndAfter
Use this boilerplate-buster when you need to perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. BeforeAndAfterEach
Use when you want to stack traits that perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. Calling get-fixture methods
If you need to create the same mutable fixture objects in multiple tests, and don't need to clean them up after using them, the simplest approach is to write one or more get-fixture methods. A get-fixture method returns a new instance of a needed fixture object (or a holder object containing multiple fixture objects) each time it is called. You can call a get-fixture method at the beginning of each test that needs the fixture, storing the returned object or objects in local variables. Here's an example:
package org.scalatest.examples.asyncfreespec.getfixture
import org.scalatest.AsyncFreeSpec import scala.concurrent.Future
class ExampleSpec extends AsyncFreeSpec {
def fixture: Future[String] = Future { "ScalaTest is " }
"Testing" - { "should be easy" in { val future = fixture val result = future map { s => s + "easy!" } result map { s => assert(s == "ScalaTest is easy!") } }
"should be fun" in { val future = fixture val result = future map { s => s + "fun!" } result map { s => assert(s == "ScalaTest is fun!") } } } }If you need to configure fixture objects differently in different tests, you can pass configuration into the get-fixture method. For example, you could pass in an initial value for a fixture object as a parameter to the get-fixture method.
Overriding
withFixture(NoArgAsyncTest)
Although the get-fixture method approach takes care of setting up a fixture at the beginning of each test, it doesn't address the problem of cleaning up a fixture at the end of the test. If you just need to perform a side-effect at the beginning or end of a test, and don't need to actually pass any fixture objects into the test, you can override
withFixture(NoArgAsyncTest)
, a method defined in traitAsyncTestSuite
, a supertrait ofAsyncFreeSpec
.Trait
AsyncFreeSpec
'srunTest
method passes a no-arg async test function towithFixture(NoArgAsyncTest)
. It iswithFixture
's responsibility to invoke that test function. The default implementation ofwithFixture
simply invokes the function and returns the result, like this:// 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 thecomplete
-lastly
syntax, defined in supertraitCompleteLastly
. Thecomplete
-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 thesuper
implementation ofwithFixture
, 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 ofFuture
's registration methods:onComplete
,onSuccess
, oronFailure
. Note that if a test fails, that will be treated as ascala.util.Success(org.scalatest.Failed)
. So if you want to perform an action if a test fails, for example, you'd register the callback usingonSuccess
.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.asyncfreespec.noargasynctest
import java.io.File import org.scalatest._ import scala.concurrent.Future
class ExampleSpec extends AsyncFreeSpec {
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) } }
"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
andworld.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 towithFixture
, in addition to anapply
method that executes the test, also includes the test name and the config map passed torunTest
. Thus you can also use the test name and configuration objects in yourwithFixture
implementation.Lastly, if you want to transform the outcome in some way in
withFixture
, you'll need to use either themap
ortransform
methods ofFuture
, 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
'sapply
method will return ascala.util.Failure
only if the test completes abruptly with a "test-fatal" exception (such asOutOfMemoryError
) that should cause the suite to abort rather than the test to fail. Thus usually you would usemap
to transform future outcomes, nottransform
, so that such test-fatal exceptions pass through unchanged. The suite will abort asynchronously with any exception returned fromNoArgAsyncTest
's apply method in ascala.util.Failure
.Calling loan-fixture methods
If you need to both pass a fixture object into a test and perform cleanup at the end of the test, you'll need to use the loan pattern. If different tests need different fixtures that require cleanup, you can implement the loan pattern directly by writing loan-fixture methods. A loan-fixture method takes a function whose body forms part or all of a test's code. It creates a fixture, passes it to the test code by invoking the function, then cleans up the fixture after the function returns.
The following example shows three tests that use two fixtures, a database and a file. Both require cleanup after, so each is provided via a loan-fixture method. (In this example, the database is simulated with a
StringBuffer
.)package org.scalatest.examples.asyncfreespec.loanfixture
import java.util.concurrent.ConcurrentHashMap
import scala.concurrent.Future import scala.concurrent.ExecutionContext
object DbServer { // Simulating a database server type Db = StringBuffer private final val databases = new ConcurrentHashMap[String, Db] def createDb(name: String): Db = { val db = new StringBuffer // java.lang.StringBuffer is thread-safe databases.put(name, db) db } def removeDb(name: String): Unit = { databases.remove(name) } }
// 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 } } }
import org.scalatest._ import DbServer._ import java.util.UUID.randomUUID
class ExampleSpec extends AsyncFreeSpec {
def withDatabase(testCode: Future[Db] => Future[Assertion]) = { val dbName = randomUUID.toString // generate a unique db name val futureDb = Future { createDb(dbName) } // create the fixture complete { val futurePopulatedDb = futureDb map { db => db.append("ScalaTest is ") // perform setup } testCode(futurePopulatedDb) // "loan" the fixture to the test code } lastly { removeDb(dbName) // ensure the fixture will be cleaned up } }
def withActor(testCode: StringActor => Future[Assertion]) = { val actor = new StringActor complete { actor ! Append("ScalaTest is ") // set up the fixture testCode(actor) // "loan" the fixture to the test code } lastly { actor ! Clear // ensure the fixture will be cleaned up } }
"Testing" - { // This test needs the actor fixture "should be productive" in { withActor { actor => actor ! Append("productive!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is productive!") } } } }
"Test code" - { // This test needs the database fixture "should be readable" in { withDatabase { futureDb => futureDb map { db => db.append("readable!") assert(db.toString == "ScalaTest is readable!") } } }
// This test needs both the actor and the database "should be clear and concise" in { withDatabase { futureDb => withActor { actor => // loan-fixture methods compose actor ! Append("concise!") val futureString = actor ? GetValue val futurePair: Future[(Db, String)] = futureDb zip futureString futurePair map { case (db, s) => db.append("clear!") assert(db.toString == "ScalaTest is clear!") assert(s == "ScalaTest is concise!") } } } } } }As demonstrated by the last test, loan-fixture methods compose. Not only do loan-fixture methods allow you to give each test the fixture it needs, they allow you to give a test multiple fixtures and clean everything up afterwards.
Also demonstrated in this example is the technique of giving each test its own "fixture sandbox" to play in. When your fixtures involve external side-effects, like creating databases, it is a good idea to give each database a unique name as is done in this example. This keeps tests completely isolated, allowing you to run them in parallel if desired.
Overriding
withFixture(OneArgTest)
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 overridingwithFixture(OneArgAsyncTest)
. Each test in afixture.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 specifyingFixtureParam
, and implement awithFixture
method that takes aOneArgAsyncTest
. ThiswithFixture
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 letwithFixture(NoArgAsyncTest)
invoke the test function instead of invoking the test function directly. To do so, you'll need to convert theOneArgAsyncTest
to aNoArgAsyncTest
. You can do that by passing the fixture object to thetoNoArgAsyncTest
method ofOneArgAsyncTest
. In other words, instead of writing “test(theFixture)
”, you'd delegate responsibility for invoking the test function to thewithFixture(NoArgAsyncTest)
method of the same instance by writing:withFixture(test.toNoArgAsyncTest(theFixture))
Here's a complete example:
package org.scalatest.examples.asyncfreespec.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.AsyncFreeSpec {
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!") } }
"should be fun" in { actor => actor ! Append("fun!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is fun!") } } } }In this example, the tests required one fixture object, a
StringActor
. If your tests need multiple fixture objects, you can simply define theFixtureParam
type to be a tuple containing the objects or, alternatively, a case class containing the objects. For more information on thewithFixture(OneArgAsyncTest)
technique, see the documentation forfixture.AsyncFreeSpec
.Mixing in
BeforeAndAfter
In all the shared fixture examples shown so far, the activities of creating, setting up, and cleaning up the fixture objects have been performed during the test. This means that if an exception occurs during any of these activities, it will be reported as a test failure. Sometimes, however, you may want setup to happen before the test starts, and cleanup after the test has completed, so that if an exception occurs during setup or cleanup, the entire suite aborts and no more tests are attempted. The simplest way to accomplish this in ScalaTest is to mix in trait
BeforeAndAfter
. With this trait you can denote a bit of code to run before each test withbefore
and/or after each test each test withafter
, like this:package org.scalatest.examples.asyncfreespec.beforeandafter
import org.scalatest.AsyncFreeSpec import org.scalatest.BeforeAndAfter 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 AsyncFreeSpec with BeforeAndAfter {
final val actor = new StringActor
before { actor ! Append("ScalaTest is ") // set up the fixture }
after { actor ! Clear // clean up the fixture }
"Testing" - { "should be easy" in { actor ! Append("easy!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is easy!") } }
"should be fun" in { actor ! Append("fun!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is fun!") } } } }Note that the only way
before
andafter
code can communicate with test code is via some side-effecting mechanism, commonly by reassigning instancevar
s or by changing the state of mutable objects held from instanceval
s (as in this example). If using instancevar
s or mutable objects held from instanceval
s you wouldn't be able to run tests in parallel in the same instance of the test class (on the JVM, not Scala.js) unless you synchronized access to the shared, mutable state.Note that on the JVM, if you override ScalaTest's default serial execution context, you will likely need to worry about synchronizing access to shared mutable fixture state, because the execution context may assign different threads to process different
Future
transformations. Although access to mutable state along the same linear chain ofFuture
transformations need not be synchronized, it can be difficult to spot cases where these constraints are violated. The best approach is to use only immutable objects when transformingFuture
s. When that's not practical, involve only thread-safe mutable objects, as is done in the above example. On Scala.js, by contrast, you need not worry about thread synchronization, because in effect only one thread exists.Although
BeforeAndAfter
provides a minimal-boilerplate way to execute code before and after tests, it isn't designed to enable stackable traits, because the order of execution would be non-obvious. If you want to factor out before and after code that is common to multiple test suites, you should use traitBeforeAndAfterEach
instead, as shown later in the next section, composing fixtures by stacking traits.Composing fixtures by stacking traits
In larger projects, teams often end up with several different fixtures that test classes need in different combinations, and possibly initialized (and cleaned up) in different orders. A good way to accomplish this in ScalaTest is to factor the individual fixtures into traits that can be composed using the stackable trait pattern. This can be done, for example, by placing
withFixture
methods in several traits, each of which callsuper.withFixture
. Here's an example in which theStringBuilderActor
andStringBufferActor
fixtures used in the previous examples have been factored out into two stackable fixture traits namedBuilder
andBuffer
:package org.scalatest.examples.asyncfreespec.composingwithasyncfixture
import org.scalatest._ import org.scalatest.SuiteMixin import collection.mutable.ListBuffer 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 StringBuilderActor { // 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 StringBufferActor { private final val buf = ListBuffer.empty[String] def !(op: StringOp): Unit = synchronized { op match { case Append(value) => buf += value case Clear => buf.clear() } } def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[List[String]] = Future { synchronized { buf.toList } } }
trait Builder extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val builderActor = new StringBuilderActor
abstract override def withFixture(test: NoArgAsyncTest) = { builderActor ! Append("ScalaTest is ") complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { builderActor ! Clear } } }
trait Buffer extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val bufferActor = new StringBufferActor
abstract override def withFixture(test: NoArgAsyncTest) = { complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { bufferActor ! Clear } } }
class ExampleSpec extends AsyncFreeSpec with Builder with Buffer {
"Testing" - { "should be easy" in { builderActor ! Append("easy!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is easy!") assert(lst.isEmpty) bufferActor ! Append("sweet") succeed } }
"should be fun" in { builderActor ! Append("fun!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is fun!") assert(lst.isEmpty) bufferActor ! Append("awesome") succeed } } } }By mixing in both the
Builder
andBuffer
traits,ExampleSpec
gets both fixtures, which will be initialized before each test and cleaned up after. The order the traits are mixed together determines the order of execution. In this case,Builder
is “super” toBuffer
. If you wantedBuffer
to be “super” toBuilder
, you need only switch the order you mix them together, like this:class Example2Spec extends AsyncFreeSpec with Buffer with Builder
If you only need one fixture you mix in only that trait:
class Example3Spec extends AsyncFreeSpec with Builder
Another way to create stackable fixture traits is by extending the
BeforeAndAfterEach
and/orBeforeAndAfterAll
traits.BeforeAndAfterEach
has abeforeEach
method that will be run before each test (like JUnit'ssetUp
), and anafterEach
method that will be run after (like JUnit'stearDown
). Similarly,BeforeAndAfterAll
has abeforeAll
method that will be run before all tests, and anafterAll
method that will be run after all tests. Here's what the previously shown example would look like if it were rewritten to use theBeforeAndAfterEach
methods instead ofwithFixture
:package org.scalatest.examples.asyncfreespec.composingbeforeandaftereach
import org.scalatest._ import org.scalatest.BeforeAndAfterEach import collection.mutable.ListBuffer 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 StringBuilderActor { // 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 StringBufferActor { private final val buf = ListBuffer.empty[String] def !(op: StringOp): Unit = synchronized { op match { case Append(value) => buf += value case Clear => buf.clear() } } def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[List[String]] = Future { synchronized { buf.toList } } }
trait Builder extends BeforeAndAfterEach { this: Suite =>
final val builderActor = new StringBuilderActor
override def beforeEach() { builderActor ! Append("ScalaTest is ") super.beforeEach() // To be stackable, must call super.beforeEach }
override def afterEach() { try super.afterEach() // To be stackable, must call super.afterEach finally builderActor ! Clear } }
trait Buffer extends BeforeAndAfterEach { this: Suite =>
final val bufferActor = new StringBufferActor
override def afterEach() { try super.afterEach() // To be stackable, must call super.afterEach finally bufferActor ! Clear } }
class ExampleSpec extends AsyncFreeSpec with Builder with Buffer {
"Testing" - {
"should be easy" in { builderActor ! Append("easy!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is easy!") assert(lst.isEmpty) bufferActor ! Append("sweet") succeed } }
"should be fun" in { builderActor ! Append("fun!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is fun!") assert(lst.isEmpty) bufferActor ! Append("awesome") succeed } } } }To get the same ordering as
withFixture
, place yoursuper.beforeEach
call at the end of eachbeforeEach
method, and thesuper.afterEach
call at the beginning of eachafterEach
method, as shown in the previous example. It is a good idea to invokesuper.afterEach
in atry
block and perform cleanup in afinally
clause, as shown in the previous example, because this ensures the cleanup code is performed even ifsuper.afterEach
throws an exception.The difference between stacking traits that extend
BeforeAndAfterEach
versus traits that implementwithFixture
is that setup and cleanup code happens before and after the test inBeforeAndAfterEach
, but at the beginning and end of the test inwithFixture
. Thus if awithFixture
method completes abruptly with an exception, it is considered a failed test. By contrast, if any of thebeforeEach
orafterEach
methods ofBeforeAndAfterEach
complete abruptly, it is considered an aborted suite, which will result in aSuiteAborted
event.Shared tests
Sometimes you may want to run the same test code on different fixture objects. In other words, you may want to write tests that are "shared" by different fixture objects. To accomplish this in an
AsyncFreeSpec
, you first place shared tests in behavior functions. These behavior functions will be invoked during the construction phase of anyAsyncFreeSpec
that uses them, so that the tests they contain will be registered as tests in thatAsyncFreeSpec
. For example, given thisStackActor
class:package org.scalatest.examples.asyncfreespec.sharedtests
import scala.collection.mutable.ListBuffer import scala.concurrent.Future import scala.concurrent.ExecutionContext
// Stack operations case class Push[T](value: T) sealed abstract class StackOp case object Pop extends StackOp case object Peek extends StackOp case object Size extends StackOp
// Stack info case class StackInfo[T](top: Option[T], size: Int, max: Int) { require(size > 0, "size was less than zero") require(max >= size, "max was less than size") val isFull: Boolean = size == max val isEmpty: Boolean = size == 0 }
class StackActor[T](Max: Int, name: String) {
private final val buf = new ListBuffer[T]
def !(push: Push[T]): Unit = synchronized { if (buf.size != Max) buf.prepend(push.value) else throw new IllegalStateException("can't push onto a full stack") }
def ?(op: StackOp)(implicit c: ExecutionContext): Future[StackInfo[T]] = synchronized { op match { case Pop => Future { if (buf.size != 0) StackInfo(Some(buf.remove(0)), buf.size, Max) else throw new IllegalStateException("can't pop an empty stack") } case Peek => Future { if (buf.size != 0) StackInfo(Some(buf(0)), buf.size, Max) else throw new IllegalStateException("can't peek an empty stack") } case Size => Future { StackInfo(None, buf.size, Max) } } }
override def toString: String = name }You may want to test the stack represented by the
StackActor
class in different states: empty, full, with one item, with one item less than capacity, etc. You may find you have several tests that make sense any time the stack is non-empty. Thus you'd ideally want to run those same tests for three stack fixture objects: a full stack, a stack with a one item, and a stack with one item less than capacity. With shared tests, you can factor these tests out into a behavior function, into which you pass the stack fixture to use when running the tests. So in yourAsyncFreeSpec
forStackActor
, you'd invoke the behavior function three times, passing in each of the three stack fixtures so that the shared tests are run for all three fixtures.You can define a behavior function that encapsulates these shared tests inside the
AsyncFreeSpec
that uses them. If they are shared between differentAsyncFreeSpec
s, however, you could also define them in a separate trait that is mixed into eachAsyncFreeSpec
that uses them. For example, here thenonEmptyStackActor
behavior function (in this case, a behavior method) is defined in a trait along with another method containing shared tests for non-full stacks:import org.scalatest.AsyncFreeSpec
trait AsyncFreeSpecStackBehaviors { this: AsyncFreeSpec =>
def nonEmptyStackActor(createNonEmptyStackActor: => StackActor[Int], lastItemAdded: Int, name: String): Unit = {
("return non-empty StackInfo when Size is fired at non-empty stack actor: " + name) in { val stackActor = createNonEmptyStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(!stackInfo.isEmpty) } }
("return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: " + name) in { val stackActor = createNonEmptyStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePeek <- stackActor ? Size afterPeek <- stackActor ? Peek } yield (beforePeek, afterPeek) futurePair map { case (beforePeek, afterPeek) => assert(afterPeek.top == Some(lastItemAdded)) assert(afterPeek.size == beforePeek.size) } }
("return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: " + name) in { val stackActor = createNonEmptyStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePop <- stackActor ? Size afterPop <- stackActor ? Pop } yield (beforePop, afterPop) futurePair map { case (beforePop, afterPop) => assert(afterPop.top == Some(lastItemAdded)) assert(afterPop.size == beforePop.size - 1) } } }
def nonFullStackActor(createNonFullStackActor: => StackActor[Int], name: String): Unit = {
("return non-full StackInfo when Size is fired at non-full stack actor: " + name) in { val stackActor = createNonFullStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(!stackInfo.isFull) } }
("return before and after StackInfo that has existing size + 1 and new item as top when Push is fired at non-full stack actor: " + name) in { val stackActor = createNonFullStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePush <- stackActor ? Size afterPush <- { stackActor ! Push(7); stackActor ? Peek } } yield (beforePush, afterPush) futurePair map { case (beforePush, afterPush) => assert(afterPush.top == Some(7)) assert(afterPush.size == beforePush.size + 1) } } } }Given these behavior functions, you could invoke them directly, but
AsyncFreeSpec
offers a DSL for the purpose, which looks like this:behave like nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName)
Here's an example:
class StackSpec extends AsyncFreeSpec with AsyncFreeSpecStackBehaviors {
val Max = 10 val LastValuePushed = Max - 1
// Stack fixture creation methods val emptyStackActorName = "empty stack actor" def emptyStackActor = new StackActor[Int](Max, emptyStackActorName )
val fullStackActorName = "full stack actor" def fullStackActor = { val stackActor = new StackActor[Int](Max, fullStackActorName ) for (i <- 0 until Max) stackActor ! Push(i) stackActor }
val almostEmptyStackActorName = "almost empty stack actor" def almostEmptyStackActor = { val stackActor = new StackActor[Int](Max, almostEmptyStackActorName ) stackActor ! Push(LastValuePushed) stackActor }
val almostFullStackActorName = "almost full stack actor" def almostFullStackActor = { val stackActor = new StackActor[Int](Max, almostFullStackActorName) for (i <- 1 to LastValuePushed) stackActor ! Push(i) stackActor }
"A Stack" - { "(when empty)" - { "should be empty" in { val stackActor = emptyStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(stackInfo.isEmpty) } }
"should complain on peek" in { recoverToSucceededIf[IllegalStateException] { emptyStackActor ? Peek } }
"should complain on pop" in { recoverToSucceededIf[IllegalStateException] { emptyStackActor ? Pop } } }
"(with one item)" - { "should" - { behave like nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName) behave like nonFullStackActor(almostEmptyStackActor, almostEmptyStackActorName) } }
"(with one item less than capacity)" - { "should" - { behave like nonEmptyStackActor(almostFullStackActor, LastValuePushed, almostFullStackActorName) behave like nonFullStackActor(almostFullStackActor, almostFullStackActorName) } }
"(full)" - {
"should be full" in { val stackActor = fullStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(stackInfo.isFull) } }
"should" - { behave like nonEmptyStackActor(fullStackActor, LastValuePushed, fullStackActorName) }
"should complain on a push" in { val stackActor = fullStackActor assertThrows[IllegalStateException] { stackActor ! Push(10) } } } } }If you load these classes into the Scala interpreter (with scalatest's JAR file on the class path), and execute it, you'll see:
scala> org.scalatest.run(new StackSpec) StackSpec: A Stack (when empty) - should be empty - should complain on peek - should complain on pop (with one item) should - return non-empty StackInfo when Size is fired at non-empty stack actor: almost empty stack actor - return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost empty stack actor - return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost empty stack actor - return non-full StackInfo when Size is fired at non-full stack actor: almost empty stack actor - return before and after StackInfo that has existing size + 1 and new item as top when Push is fired at non-full stack actor: almost empty stack actor (with one item less than capacity) should - return non-empty StackInfo when Size is fired at non-empty stack actor: almost full stack actor - return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost full stack actor - return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost full stack actor - return non-full StackInfo when Size is fired at non-full stack actor: almost full stack actor - return before and after StackInfo that has existing size + 1 and new item as top when Push is fired at non-full stack actor: almost full stack actor (full) - should be full should - return non-empty StackInfo when Size is fired at non-empty stack actor: full stack actor - return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: full stack actor - return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: full stack actor - should complain on a push
One thing to keep in mind when using shared tests is that in ScalaTest, each test in a suite must have a unique name. If you register the same tests repeatedly in the same suite, one problem you may encounter is an exception at runtime complaining that multiple tests are being registered with the same test name. Although in an
AsyncFreeSpec
, the-
clause is a nesting construct analogous toAsyncFunSpec
'sdescribe
clause, you many sometimes need to do a bit of extra work to ensure that the test names are unique. If a duplicate test name problem shows up in anAsyncFreeSpec
, you'll need to pass in a prefix or suffix string to add to each test name. You can calltoString
on the shared fixture object, or pass this string the same way you pass any other data needed by the shared tests. This is the approach taken by the previousAsyncFreeSpecStackBehaviors
example.Given this
AsyncFreeSpecStackBehaviors
trait, calling it with thealmostEmptyStackActor
fixture, like this:behave like nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName)
yields test names:
A Stack (when non-empty) should return non-empty StackInfo when Size is fired at non-empty stack actor: almost empty stack actor
A Stack (when non-empty) should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost empty stack actor
A Stack (when non-empty) should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost empty stack actor
Whereas calling it with the
almostFullStackActor
fixture, like this:behave like nonEmptyStackActor(almostFullStackActor, LastValuePushed, almostFullStackActorName)
yields different test names:
A Stack (when non-empty) should return non-empty StackInfo when Size is fired at non-empty stack actor: almost full stack actor
A Stack (when non-empty) should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost full stack actor
A Stack (when non-empty) should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost full stack actor
- trait AsyncFreeSpecLike extends AsyncTestSuite with AsyncTestRegistration with Informing with Notifying with Alerting with Documenting
Implementation trait for class
AsyncFreeSpec
, which facilitates a “behavior-driven” style of development (BDD), in which tests are nested inside text clauses denoted with the dash operator (-
).Implementation trait for class
AsyncFreeSpec
, which facilitates a “behavior-driven” style of development (BDD), in which tests are nested inside text clauses denoted with the dash operator (-
).AsyncFreeSpec
is a class, not a trait, to minimize compile time given there is a slight compiler overhead to mixing in traits compared to extending classes. If you need to mix the behavior ofAsyncFreeSpec
into some other class, you can use this trait instead, because classAsyncFreeSpec
does nothing more than extend this trait and add a nicetoString
implementation.See the documentation of the class for a detailed overview of
AsyncFreeSpec
. - abstract class AsyncFunSpec extends AsyncFunSpecLike
Enables testing of asynchronous code without blocking, using a style consistent with traditional
FunSpec
tests.Enables testing of asynchronous code without blocking, using a style consistent with traditional
FunSpec
tests.Recommended Usage: AsyncFunSpec
is intended to enable users ofFunSpec
to write non-blocking asynchronous tests that are consistent with their traditionalFunSpec
tests. Note:AsyncFunSpec
is intended for use in special situations where non-blocking asynchronous testing is needed, with classFunSpec
used for general needs.Given a
Future
returned by the code you are testing, you need not block until theFuture
completes before performing assertions against its value. You can instead map those assertions onto theFuture
and return the resultingFuture[Assertion]
to ScalaTest. The test will complete asynchronously, when theFuture[Assertion]
completes.Here's an example
AsyncFunSpec
:package org.scalatest.examples.asyncfunspec
import org.scalatest.AsyncFunSpec import scala.concurrent.Future
class AddSpec extends AsyncFunSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
describe("addSoon") { it("will eventually compute a sum of passed Ints") { 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
describe("addNow") { it("will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } } }An
AsyncFunSpec
contains describe clauses and tests. You define a describe clause withdescribe
, and a test with eitherit
orthey
.describe
,it
, andthey
are methods, defined inAsyncFunSpec
, which will be invoked by the primary constructor ofAddSpec
. A describe clause names, or gives more information about, the subject (class or other entity) you are specifying and testing. In the previous example,"addSoon"
and"addNow"
are the subjects under specification and test. With each test you provide a string (the spec text) that specifies one bit of behavior of the subject, and a block of code that tests that behavior. You place the spec text between the parentheses, followed by the test code between curly braces. The test code will be wrapped up as a function passed as a by-name parameter toit
(orthey
), which will register the test for later execution.Note: the
they
method is intended for use when the subject is plural, for example:describe("The combinators") { they("should be easy to learn") { succeed } they("should be efficient") { succeed } they("should do something cool") { succeed } }
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, isFuture[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 toFuture[Assertion]
and registered. The implicit conversion is fromAssertion
toFuture[Assertion]
, so you must end synchronous tests in some ScalaTest assertion or matcher expression. If a test would not otherwise end in typeAssertion
, you can placesucceed
at the end of the test.succeed
, a field in traitAssertions
, returns theSucceeded
singleton:scala> succeed res2: org.scalatest.Assertion = Succeeded
Thus placing
succeed
at the end of a test body will satisfy the type checker:it("will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) assert(sum == 3) println("hi") // println has result type Unit succeed // succeed has result type Assertion }
An
AsyncFunSpec
's lifecycle has two phases: the registration phase and the ready phase. It starts in registration phase and enters ready phase the first timerun
is called on it. It then remains in ready phase for the remainder of its lifetime.Tests can only be registered with the
it
method while theAsyncFunSpec
is in its registration phase. Any attempt to register a test after theAsyncFunSpec
has entered its ready phase, i.e., afterrun
has been invoked on theAsyncFunSpec
, will be met with a thrownTestRegistrationClosedException
. The recommended style of usingAsyncFunSpec
is to register tests during object construction as is done in all the examples shown here. If you keep to the recommended style, you should never see aTestRegistrationClosedException
.Asynchronous execution model
AsyncFunSpec
extendsAsyncTestSuite
, which provides an implicitscala.concurrent.ExecutionContext
namedexecutionContext
. This execution context is used byAsyncFunSpec
to transform theFuture[Assertion]
s returned by each test into theFutureOutcome
returned by thetest
function passed towithFixture
. ThisExecutionContext
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 theFutureOutcome
returned from thetest
function. Other tasks that will be queued are any transformations of, or callbacks registered on,Future
s that occur in your test body, including any assertions you map ontoFuture
s. 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 ofFuture
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]
. ThisFuture[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 theFuture[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
FunSpec
withScalaFutures
instead. Alternatively, you could override theexecutionContext
and use a traditionalExecutionContext
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 therunNow
execution context on Scala.js instead of the defaultqueue
, you would write:// on Scala.js implicit override def executionContext = org.scalatest.concurrent.TestExecutionContext.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 anAsyncFunSpec
will be executed one after another, i.e., serially. This is true whether those tests returnAssertion
orFuture[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
AsyncFunSpec
to be executed in parallel, you must mix inParallelTestExecution
and enable parallel execution of tests in your build. You enable parallel execution inRunner
with the-P
command line flag. In the ScalaTest Maven Plugin, setparallel
totrue
. Insbt
, 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 theDistributor
, and allowed to complete in parallel, using threads from theexecutionContext
. If you are using ScalaTest's serial execution context, the JVM default, asynchronous tests will run in parallel very much like traditional (such asFunSpec
) tests run in parallel: 1) BecauseParallelTestExecution
extendsOneInstancePerTest
, 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 ofFuture
s 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 invokedrun
, 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 theexecutionContext
. 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 inParallelTestExecution
is executed: the tests will run sequentially. If you use an execution context backed by a thread-pool, such asglobal
, 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
andrecoverToExceptionIf
methods of traitRecoverMethods
. Because this trait is mixed into supertraitAsyncTestSuite
, both of these methods are available by default in anAsyncFunSpec
.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 toassertThrows
, except in the context of a future. It transforms aFuture
of any type into aFuture[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 therecoverToSucceededIf
in its behavior when the assertion succeeds:recoverToSucceededIf
yields aFuture[Assertion]
, whereasrecoverToExceptionIf
yields aFuture[T]
, whereT
is the expected exception type.recoverToExceptionIf[IllegalStateException] { // Result type: Future[IllegalStateException] emptyStackActor ? Peek }
In other words,
recoverToExpectionIf
is tointercept
asrecovertToSucceededIf
is toassertThrows
. 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 showingrecoverToExceptionIf
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]"))
Ignored tests
To support the common use case of temporarily disabling a test, with the good intention of resurrecting the test at a later time,
AsyncFunSpec
provides registration methods that start withignore
instead ofit
orthey
. For example, to temporarily disable the test with the text"will eventually compute a sum of passed Ints"
, just change “it
” into “ignore
,” like this:package org.scalatest.examples.asyncfunspec.ignore
import org.scalatest.AsyncFunSpec import scala.concurrent.Future
class AddSpec extends AsyncFunSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
describe("addSoon") { ignore("will eventually compute a sum of passed Ints") { 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
describe("addNow") { it("will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } } }If you run this version of
AddSpec
with:scala> org.scalatest.run(new AddSpec)
It will run only the second test and report that the first test was ignored:
AddSpec: addSoon - will eventually compute a sum of passed Ints !!! IGNORED !!! addNow - will immediately compute a sum of passed Ints
If you wish to temporarily ignore an entire suite of tests, you can (on the JVM, not Scala.js) annotate the test class with
@Ignore
, like this:package org.scalatest.examples.asyncfunspec.ignoreall
import org.scalatest.AsyncFunSpec import scala.concurrent.Future import org.scalatest.Ignore
@Ignore class AddSpec extends AsyncFunSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
describe("addSoon") { it("will eventually compute a sum of passed Ints") { 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
describe("addNow") { it("will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } } }When you mark a test class with a tag annotation, ScalaTest will mark each test defined in that class with that tag. Thus, marking the
AddSpec
in the above example with the@Ignore
tag annotation means that both tests in the class will be ignored. If you run the aboveAddSpec
in the Scala interpreter, you'll see:AddSpec: addSoon - will eventually compute a sum of passed Ints !!! IGNORED !!! addNow - will immediately compute a sum of passed Ints !!! IGNORED !!!
Note that marking a test class as ignored won't prevent it from being discovered by ScalaTest. Ignored classes will be discovered and run, and all their tests will be reported as ignored. This is intended to keep the ignored class visible, to encourage the developers to eventually fix and “un-ignore” it. If you want to prevent a class from being discovered at all (on the JVM, not Scala.js), use the
DoNotDiscover
annotation instead.If you want to ignore all tests of a suite on Scala.js, where annotations can't be inspected at runtime, you'll need to change
it
toignore
at each test site. To make a suite non-discoverable on Scala.js, ensure it does not declare a public no-arg constructor. You can either declare a public constructor that takes one or more arguments, or make the no-arg constructor non-public. Because this technique will also make the suite non-discoverable on the JVM, it is a good approach for suites you want to run (but not be discoverable) on both Scala.js and the JVM.Informers
One of the parameters to
AsyncFunSpec
'srun
method is aReporter
, which will collect and report information about the running suite of tests. Information about suites and tests that were run, whether tests succeeded or failed, and tests that were ignored will be passed to theReporter
as the suite runs. Most often the reporting done by default byAsyncFunSpec
's methods will be sufficient, but occasionally you may wish to provide custom information to theReporter
from a test. For this purpose, anInformer
that will forward information to the currentReporter
is provided via theinfo
parameterless method. You can pass the extra information to theInformer
via one of itsapply
methods. TheInformer
will then pass the information to theReporter
via anInfoProvided
event. Here's an example in which theInformer
returned byinfo
is used implicitly by theGiven
,When
, andThen
methods of traitGivenWhenThen
:package org.scalatest.examples.asyncfunspec.info
import collection.mutable import org.scalatest._
class SetSpec extends AsyncFunSpec with GivenWhenThen {
describe("A mutable Set") { it("should allow an element to be added") { Given("an empty mutable Set") val set = mutable.Set.empty[String]
When("an element is added") set += "clarity"
Then("the Set should have size 1") assert(set.size === 1)
And("the Set should contain the added element") assert(set.contains("clarity"))
info("That's all folks!") succeed } } }If you run this
AsyncFunSpec
from the interpreter, you will see the following output:scala> org.scalatest.run(new SetSpec) A mutable Set - should allow an element to be added + Given an empty mutable Set + When an element is added + Then the Set should have size 1 + And the Set should contain the added element + That's all folks!
Documenters
AsyncFunSpec
also provides amarkup
method that returns aDocumenter
, which allows you to send to theReporter
text formatted in Markdown syntax. You can pass the extra information to theDocumenter
via itsapply
method. TheDocumenter
will then pass the information to theReporter
via anMarkupProvided
event.Here's an example
AsyncFunSpec
that usesmarkup
:package org.scalatest.examples.asyncfunspec.markup
import collection.mutable import org.scalatest._
class SetSpec extends AsyncFunSpec with GivenWhenThen {
markup { """ Mutable Set ———-- A set is a collection that contains no duplicate elements. To implement a concrete mutable set, you need to provide implementations of the following methods: def contains(elem: A): Boolean def iterator: Iterator[A] def += (elem: A): this.type def -= (elem: A): this.type If you wish that methods like `take`, `drop`, `filter` return the same kind of set, you should also override: def empty: This It is also good idea to override methods `foreach` and `size` for efficiency. """ }
describe("A mutable Set") { it("should allow an element to be added") { Given("an empty mutable Set") val set = mutable.Set.empty[String]
When("an element is added") set += "clarity"
Then("the Set should have size 1") assert(set.size === 1)
And("the Set should contain the added element") assert(set.contains("clarity"))
markup("This test finished with a **bold** statement!") succeed } } }Although all of ScalaTest's built-in reporters will display the markup text in some form, the HTML reporter will format the markup information into HTML. Thus, the main purpose of
markup
is to add nicely formatted text to HTML reports. Here's what the aboveSetSpec
would look like in the HTML reporter:Notifiers and alerters
ScalaTest records text passed to
info
andmarkup
during tests, and sends the recorded text in therecordedEvents
field of test completion events likeTestSucceeded
andTestFailed
. This allows string reporters (like the standard out reporter) to showinfo
andmarkup
text after the test name in a color determined by the outcome of the test. For example, if the test fails, string reporters will show theinfo
andmarkup
text in red. If a test succeeds, string reporters will show theinfo
andmarkup
text in green. While this approach helps the readability of reports, it means that you can't useinfo
to get status updates from long running tests.To get immediate (i.e., non-recorded) notifications from tests, you can use
note
(aNotifier
) andalert
(anAlerter
). Here's an example showing the differences:package org.scalatest.examples.asyncfunspec.note
import collection.mutable import org.scalatest._
class SetSpec extends AsyncFunSpec {
describe("A mutable Set") { it("should allow an element to be added") {
info("info is recorded") markup("markup is *also* recorded") note("notes are sent immediately") alert("alerts are also sent immediately")
val set = mutable.Set.empty[String] set += "clarity" assert(set.size === 1) assert(set.contains("clarity")) } } }Because
note
andalert
information is sent immediately, it will appear before the test name in string reporters, and its color will be unrelated to the ultimate outcome of the test:note
text will always appear in green,alert
text will always appear in yellow. Here's an example:scala> org.scalatest.run(new SetSpec) SetSpec: A mutable Set + notes are sent immediately + alerts are also sent immediately - should allow an element to be added + info is recorded + markup is *also* recorded
Another example is slowpoke notifications. If you find a test is taking a long time to complete, but you're not sure which test, you can enable slowpoke notifications. ScalaTest will use an
Alerter
to fire an event whenever a test has been running longer than a specified amount of time.In summary, use
info
andmarkup
for text that should form part of the specification output. Usenote
andalert
to send status notifications. (Because the HTML reporter is intended to produce a readable, printable specification,info
andmarkup
text will appear in the HTML report, butnote
andalert
text will not.)Pending tests
A pending test is one that has been given a name but is not yet implemented. The purpose of pending tests is to facilitate a style of testing in which documentation of behavior is sketched out before tests are written to verify that behavior (and often, before the behavior of the system being tested is itself implemented). Such sketches form a kind of specification of what tests and functionality to implement later.
To support this style of testing, a test can be given a name that specifies one bit of behavior required by the system being tested. At the end of the test, it can call method
pending
, which will cause it to complete abruptly withTestPendingException
.Because tests in ScalaTest can be designated as pending with
TestPendingException
, both the test name and any information sent to the reporter when running the test can appear in the report of a test run. (In other words, the code of a pending test is executed just like any other test.) However, because the test completes abruptly withTestPendingException
, the test will be reported as pending, to indicate the actual test, and possibly the functionality, has not yet been implemented. Here's an example:package org.scalatest.examples.asyncfunspec.pending
import org.scalatest.AsyncFunSpec import scala.concurrent.Future
class AddSpec extends AsyncFunSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
describe("addSoon") { it("will eventually compute a sum of passed Ints")(pending) }
def addNow(addends: Int*): Int = addends.sum
describe("addNow") { it("will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } } }(Note: "
(pending)
" is the body of the test. Thus the test contains just one statement, an invocation of thepending
method, which throwsTestPendingException
.) If you run this version ofAddSpec
with:scala> org.scalatest.run(new AddSpec)
It will run both tests, but report that first test is pending. You'll see:
AddSpec: addSoon - will eventually compute a sum of passed Ints (pending) addNow - will immediately compute a sum of passed Ints
One difference between an ignored test and a pending one is that an ignored test is intended to be used during significant refactorings of the code under test, when tests break and you don't want to spend the time to fix all of them immediately. You can mark some of those broken tests as ignored temporarily, so that you can focus the red bar on just failing tests you actually want to fix immediately. Later you can go back and fix the ignored tests. In other words, by ignoring some failing tests temporarily, you can more easily notice failed tests that you actually want to fix. By contrast, a pending test is intended to be used before a test and/or the code under test is written. Pending indicates you've decided to write a test for a bit of behavior, but either you haven't written the test yet, or have only written part of it, or perhaps you've written the test but don't want to implement the behavior it tests until after you've implemented a different bit of behavior you realized you need first. Thus ignored tests are designed to facilitate refactoring of existing code whereas pending tests are designed to facilitate the creation of new code.
One other difference between ignored and pending tests is that ignored tests are implemented as a test tag that is excluded by default. Thus an ignored test is never executed. By contrast, a pending test is implemented as a test that throws
TestPendingException
(which is what calling thepending
method does). Thus the body of pending tests are executed up until they throwTestPendingException
.Tagging tests
An
AsyncFunSpec
's tests may be classified into groups by tagging them with string names. As with any suite, when executing anAsyncFunSpec
, groups of tests can optionally be included and/or excluded. To tag anAsyncFunSpec
's tests, you pass objects that extend classorg.scalatest.Tag
to methods that register tests. ClassTag
takes one parameter, a string name. If you have created tag annotation interfaces as described in theTag
documentation, then you will probably want to use tag names on your test functions that match. To do so, simply pass the fully qualified names of the tag interfaces to theTag
constructor. For example, if you've defined a tag annotation interface with fully qualified name,com.mycompany.tags.DbTest
, then you could create a matching tag forAsyncFunSpec
s like this:package org.scalatest.examples.asyncfunspec.tagging
import org.scalatest.Tag
object DbTest extends Tag("com.mycompany.tags.DbTest")Given these definitions, you could place
AsyncFunSpec
tests into groups with tags like this:import org.scalatest.AsyncFunSpec import org.scalatest.tagobjects.Slow import scala.concurrent.Future
class AddSpec extends AsyncFunSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
describe("addSoon") { it("will eventually compute a sum of passed Ints", Slow) { 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
describe("addNow") { it("will immediately compute a sum of passed Ints", Slow, DbTest) {
val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } } }This code marks both tests with the
org.scalatest.tags.Slow
tag, and the second test with thecom.mycompany.tags.DbTest
tag.The
run
method takes aFilter
, whose constructor takes an optionalSet[String]
calledtagsToInclude
and aSet[String]
calledtagsToExclude
. IftagsToInclude
isNone
, all tests will be run except those those belonging to tags listed in thetagsToExclude
Set
. IftagsToInclude
is defined, only tests belonging to tags mentioned in thetagsToInclude
set, and not mentioned intagsToExclude
, will be run.It is recommended, though not required, that you create a corresponding tag annotation when you create a
Tag
object. A tag annotation (on the JVM, not Scala.js) allows you to tag all the tests of anAsyncFunSpec
in one stroke by annotating the class. For more information and examples, see the documentation for classTag
. On Scala.js, to tag all tests of a suite, you'll need to tag each test individually at the test site.Shared fixtures
A test fixture is composed of the objects and other artifacts (files, sockets, database connections, etc.) tests use to do their work. When multiple tests need to work with the same fixtures, it is important to try and avoid duplicating the fixture code across those tests. The more code duplication you have in your tests, the greater drag the tests will have on refactoring the actual production code.
ScalaTest recommends three techniques to eliminate such code duplication in async styles:
- Refactor using Scala
- Override
withFixture
- Mix in a before-and-after trait
Each technique is geared towards helping you reduce code duplication without introducing instance
var
s, shared mutable objects, or other dependencies between tests. Eliminating shared mutable state across tests will make your test code easier to reason about and eliminate the need to synchronize access to shared mutable state on the JVM.The following sections describe these techniques, including explaining the recommended usage for each. But first, here's a table summarizing the options:
Refactor using Scala when different tests need different fixtures. get-fixture methods The extract method refactor helps you create a fresh instances of mutable fixture objects in each test that needs them, but doesn't help you clean them up when you're done. loan-fixture methods Factor out dupicate code with the loan pattern when different tests need different fixtures that must be cleaned up afterwards. Override withFixture
when most or all tests need the same fixture.withFixture(NoArgAsyncTest)
The recommended default approach when most or all tests need the same fixture treatment. This general technique allows you, for example, to perform side effects at the beginning and end of all or most tests, transform the outcome of tests, retry tests, make decisions based on test names, tags, or other test data. Use this technique unless: - Different tests need different fixtures (refactor using Scala instead)
- An exception in fixture code should abort the suite, not fail the test (use a before-and-after trait instead)
- You have objects to pass into tests (override
withFixture(OneArgAsyncTest)
instead)
withFixture(OneArgAsyncTest)
Use when you want to pass the same fixture object or objects as a parameter into all or most tests. Mix in a before-and-after trait when you want an aborted suite, not a failed test, if the fixture code fails. BeforeAndAfter
Use this boilerplate-buster when you need to perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. BeforeAndAfterEach
Use when you want to stack traits that perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. Calling get-fixture methods
If you need to create the same mutable fixture objects in multiple tests, and don't need to clean them up after using them, the simplest approach is to write one or more get-fixture methods. A get-fixture method returns a new instance of a needed fixture object (or a holder object containing multiple fixture objects) each time it is called. You can call a get-fixture method at the beginning of each test that needs the fixture, storing the returned object or objects in local variables. Here's an example:
package org.scalatest.examples.asyncfunspec.getfixture
import org.scalatest.AsyncFunSpec import scala.concurrent.Future
class ExampleSpec extends AsyncFunSpec {
def fixture: Future[String] = Future { "ScalaTest is " }
describe("Testing") { it("should be easy") { val future = fixture val result = future map { s => s + "easy!" } result map { s => assert(s == "ScalaTest is easy!") } }
it("should be fun") { val future = fixture val result = future map { s => s + "fun!" } result map { s => assert(s == "ScalaTest is fun!") } } } }If you need to configure fixture objects differently in different tests, you can pass configuration into the get-fixture method. For example, you could pass in an initial value for a fixture object as a parameter to the get-fixture method.
Overriding
withFixture(NoArgAsyncTest)
Although the get-fixture method approach takes care of setting up a fixture at the beginning of each test, it doesn't address the problem of cleaning up a fixture at the end of the test. If you just need to perform a side-effect at the beginning or end of a test, and don't need to actually pass any fixture objects into the test, you can override
withFixture(NoArgAsyncTest)
, a method defined in traitAsyncTestSuite
, a supertrait ofAsyncFunSpec
.Trait
AsyncFunSpec
'srunTest
method passes a no-arg async test function towithFixture(NoArgAsyncTest)
. It iswithFixture
's responsibility to invoke that test function. The default implementation ofwithFixture
simply invokes the function and returns the result, like this:// 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 thecomplete
-lastly
syntax, defined in supertraitCompleteLastly
. Thecomplete
-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 thesuper
implementation ofwithFixture
, 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 ofFuture
's registration methods:onComplete
,onSuccess
, oronFailure
. Note that if a test fails, that will be treated as ascala.util.Success(org.scalatest.Failed)
. So if you want to perform an action if a test fails, for example, you'd register the callback usingonSuccess
.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.asyncfunspec.noargasynctest
import java.io.File import org.scalatest._ import scala.concurrent.Future
class ExampleSpec extends AsyncFunSpec {
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 }
describe("This test") { it("should succeed") { addSoon(1, 1) map { sum => assert(sum == 2) } }
it("should fail") { addSoon(1, 1) map { sum => assert(sum == 3) } } } }Running this version of
ExampleSpec
in the interpreter in a directory with two files,hello.txt
andworld.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 towithFixture
, in addition to anapply
method that executes the test, also includes the test name and the config map passed torunTest
. Thus you can also use the test name and configuration objects in yourwithFixture
implementation.Lastly, if you want to transform the outcome in some way in
withFixture
, you'll need to use either themap
ortransform
methods ofFuture
, 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
'sapply
method will return ascala.util.Failure
only if the test completes abruptly with a "test-fatal" exception (such asOutOfMemoryError
) that should cause the suite to abort rather than the test to fail. Thus usually you would usemap
to transform future outcomes, nottransform
, so that such test-fatal exceptions pass through unchanged. The suite will abort asynchronously with any exception returned fromNoArgAsyncTest
's apply method in ascala.util.Failure
.Calling loan-fixture methods
If you need to both pass a fixture object into a test and perform cleanup at the end of the test, you'll need to use the loan pattern. If different tests need different fixtures that require cleanup, you can implement the loan pattern directly by writing loan-fixture methods. A loan-fixture method takes a function whose body forms part or all of a test's code. It creates a fixture, passes it to the test code by invoking the function, then cleans up the fixture after the function returns.
The following example shows three tests that use two fixtures, a database and a file. Both require cleanup after, so each is provided via a loan-fixture method. (In this example, the database is simulated with a
StringBuffer
.)package org.scalatest.examples.asyncfunspec.loanfixture
import java.util.concurrent.ConcurrentHashMap
import scala.concurrent.Future import scala.concurrent.ExecutionContext
object DbServer { // Simulating a database server type Db = StringBuffer private final val databases = new ConcurrentHashMap[String, Db] def createDb(name: String): Db = { val db = new StringBuffer // java.lang.StringBuffer is thread-safe databases.put(name, db) db } def removeDb(name: String): Unit = { databases.remove(name) } }
// 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 } } }
import org.scalatest._ import DbServer._ import java.util.UUID.randomUUID
class ExampleSpec extends AsyncFunSpec {
def withDatabase(testCode: Future[Db] => Future[Assertion]) = { val dbName = randomUUID.toString // generate a unique db name val futureDb = Future { createDb(dbName) } // create the fixture complete { val futurePopulatedDb = futureDb map { db => db.append("ScalaTest is ") // perform setup } testCode(futurePopulatedDb) // "loan" the fixture to the test code } lastly { removeDb(dbName) // ensure the fixture will be cleaned up } }
def withActor(testCode: StringActor => Future[Assertion]) = { val actor = new StringActor complete { actor ! Append("ScalaTest is ") // set up the fixture testCode(actor) // "loan" the fixture to the test code } lastly { actor ! Clear // ensure the fixture will be cleaned up } }
describe("Testing") { // This test needs the actor fixture it("should be productive") { withActor { actor => actor ! Append("productive!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is productive!") } } } }
describe("Test code") { // This test needs the database fixture it("should be readable") { withDatabase { futureDb => futureDb map { db => db.append("readable!") assert(db.toString == "ScalaTest is readable!") } } }
// This test needs both the actor and the database it("should be clear and concise") { withDatabase { futureDb => withActor { actor => // loan-fixture methods compose actor ! Append("concise!") val futureString = actor ? GetValue val futurePair: Future[(Db, String)] = futureDb zip futureString futurePair map { case (db, s) => db.append("clear!") assert(db.toString == "ScalaTest is clear!") assert(s == "ScalaTest is concise!") } } } } } }As demonstrated by the last test, loan-fixture methods compose. Not only do loan-fixture methods allow you to give each test the fixture it needs, they allow you to give a test multiple fixtures and clean everything up afterwards.
Also demonstrated in this example is the technique of giving each test its own "fixture sandbox" to play in. When your fixtures involve external side-effects, like creating databases, it is a good idea to give each database a unique name as is done in this example. This keeps tests completely isolated, allowing you to run them in parallel if desired.
Overriding
withFixture(OneArgTest)
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 overridingwithFixture(OneArgAsyncTest)
. Each test in afixture.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 specifyingFixtureParam
, and implement awithFixture
method that takes aOneArgAsyncTest
. ThiswithFixture
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 letwithFixture(NoArgAsyncTest)
invoke the test function instead of invoking the test function directly. To do so, you'll need to convert theOneArgAsyncTest
to aNoArgAsyncTest
. You can do that by passing the fixture object to thetoNoArgAsyncTest
method ofOneArgAsyncTest
. In other words, instead of writing “test(theFixture)
”, you'd delegate responsibility for invoking the test function to thewithFixture(NoArgAsyncTest)
method of the same instance by writing:withFixture(test.toNoArgAsyncTest(theFixture))
Here's a complete example:
package org.scalatest.examples.asyncfunspec.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.AsyncFunSpec {
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 } }
describe("Testing") { it("should be easy") { actor => actor ! Append("easy!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is easy!") } }
it("should be fun") { actor => actor ! Append("fun!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is fun!") } } } }In this example, the tests required one fixture object, a
StringActor
. If your tests need multiple fixture objects, you can simply define theFixtureParam
type to be a tuple containing the objects or, alternatively, a case class containing the objects. For more information on thewithFixture(OneArgAsyncTest)
technique, see the documentation forfixture.AsyncFunSpec
.Mixing in
BeforeAndAfter
In all the shared fixture examples shown so far, the activities of creating, setting up, and cleaning up the fixture objects have been performed during the test. This means that if an exception occurs during any of these activities, it will be reported as a test failure. Sometimes, however, you may want setup to happen before the test starts, and cleanup after the test has completed, so that if an exception occurs during setup or cleanup, the entire suite aborts and no more tests are attempted. The simplest way to accomplish this in ScalaTest is to mix in trait
BeforeAndAfter
. With this trait you can denote a bit of code to run before each test withbefore
and/or after each test each test withafter
, like this:package org.scalatest.examples.asyncfunspec.beforeandafter
import org.scalatest.AsyncFunSpec import org.scalatest.BeforeAndAfter 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 AsyncFunSpec with BeforeAndAfter {
final val actor = new StringActor
before { actor ! Append("ScalaTest is ") // set up the fixture }
after { actor ! Clear // clean up the fixture }
describe("Testing") { it("should be easy") { actor ! Append("easy!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is easy!") } }
it("should be fun") { actor ! Append("fun!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is fun!") } } } }Note that the only way
before
andafter
code can communicate with test code is via some side-effecting mechanism, commonly by reassigning instancevar
s or by changing the state of mutable objects held from instanceval
s (as in this example). If using instancevar
s or mutable objects held from instanceval
s you wouldn't be able to run tests in parallel in the same instance of the test class (on the JVM, not Scala.js) unless you synchronized access to the shared, mutable state.Note that on the JVM, if you override ScalaTest's default serial execution context, you will likely need to worry about synchronizing access to shared mutable fixture state, because the execution context may assign different threads to process different
Future
transformations. Although access to mutable state along the same linear chain ofFuture
transformations need not be synchronized, it can be difficult to spot cases where these constraints are violated. The best approach is to use only immutable objects when transformingFuture
s. When that's not practical, involve only thread-safe mutable objects, as is done in the above example. On Scala.js, by contrast, you need not worry about thread synchronization, because in effect only one thread exists.Although
BeforeAndAfter
provides a minimal-boilerplate way to execute code before and after tests, it isn't designed to enable stackable traits, because the order of execution would be non-obvious. If you want to factor out before and after code that is common to multiple test suites, you should use traitBeforeAndAfterEach
instead, as shown later in the next section, composing fixtures by stacking traits.Composing fixtures by stacking traits
In larger projects, teams often end up with several different fixtures that test classes need in different combinations, and possibly initialized (and cleaned up) in different orders. A good way to accomplish this in ScalaTest is to factor the individual fixtures into traits that can be composed using the stackable trait pattern. This can be done, for example, by placing
withFixture
methods in several traits, each of which callsuper.withFixture
. Here's an example in which theStringBuilderActor
andStringBufferActor
fixtures used in the previous examples have been factored out into two stackable fixture traits namedBuilder
andBuffer
:package org.scalatest.examples.asyncfunspec.composingwithasyncfixture
import org.scalatest._ import org.scalatest.SuiteMixin import collection.mutable.ListBuffer 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 StringBuilderActor { // 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 StringBufferActor { private final val buf = ListBuffer.empty[String] def !(op: StringOp): Unit = synchronized { op match { case Append(value) => buf += value case Clear => buf.clear() } } def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[List[String]] = Future { synchronized { buf.toList } } }
trait Builder extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val builderActor = new StringBuilderActor
abstract override def withFixture(test: NoArgAsyncTest) = { builderActor ! Append("ScalaTest is ") complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { builderActor ! Clear } } }
trait Buffer extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val bufferActor = new StringBufferActor
abstract override def withFixture(test: NoArgAsyncTest) = { complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { bufferActor ! Clear } } }
class ExampleSpec extends AsyncFunSpec with Builder with Buffer {
describe("Testing") { it("should be easy") { builderActor ! Append("easy!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is easy!") assert(lst.isEmpty) bufferActor ! Append("sweet") succeed } }
it("should be fun") { builderActor ! Append("fun!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is fun!") assert(lst.isEmpty) bufferActor ! Append("awesome") succeed } } } }By mixing in both the
Builder
andBuffer
traits,ExampleSpec
gets both fixtures, which will be initialized before each test and cleaned up after. The order the traits are mixed together determines the order of execution. In this case,Builder
is “super” toBuffer
. If you wantedBuffer
to be “super” toBuilder
, you need only switch the order you mix them together, like this:class Example2Spec extends AsyncFunSpec with Buffer with Builder
If you only need one fixture you mix in only that trait:
class Example3Spec extends AsyncFunSpec with Builder
Another way to create stackable fixture traits is by extending the
BeforeAndAfterEach
and/orBeforeAndAfterAll
traits.BeforeAndAfterEach
has abeforeEach
method that will be run before each test (like JUnit'ssetUp
), and anafterEach
method that will be run after (like JUnit'stearDown
). Similarly,BeforeAndAfterAll
has abeforeAll
method that will be run before all tests, and anafterAll
method that will be run after all tests. Here's what the previously shown example would look like if it were rewritten to use theBeforeAndAfterEach
methods instead ofwithFixture
:package org.scalatest.examples.asyncfunspec.composingbeforeandaftereach
import org.scalatest._ import org.scalatest.BeforeAndAfterEach import collection.mutable.ListBuffer 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 StringBuilderActor { // 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 StringBufferActor { private final val buf = ListBuffer.empty[String] def !(op: StringOp): Unit = synchronized { op match { case Append(value) => buf += value case Clear => buf.clear() } } def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[List[String]] = Future { synchronized { buf.toList } } }
trait Builder extends BeforeAndAfterEach { this: Suite =>
final val builderActor = new StringBuilderActor
override def beforeEach() { builderActor ! Append("ScalaTest is ") super.beforeEach() // To be stackable, must call super.beforeEach }
override def afterEach() { try super.afterEach() // To be stackable, must call super.afterEach finally builderActor ! Clear } }
trait Buffer extends BeforeAndAfterEach { this: Suite =>
final val bufferActor = new StringBufferActor
override def afterEach() { try super.afterEach() // To be stackable, must call super.afterEach finally bufferActor ! Clear } }
class ExampleSpec extends AsyncFunSpec with Builder with Buffer {
describe("Testing") {
it("should be easy") { builderActor ! Append("easy!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is easy!") assert(lst.isEmpty) bufferActor ! Append("sweet") succeed } }
it("should be fun") { builderActor ! Append("fun!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is fun!") assert(lst.isEmpty) bufferActor ! Append("awesome") succeed } } } }To get the same ordering as
withFixture
, place yoursuper.beforeEach
call at the end of eachbeforeEach
method, and thesuper.afterEach
call at the beginning of eachafterEach
method, as shown in the previous example. It is a good idea to invokesuper.afterEach
in atry
block and perform cleanup in afinally
clause, as shown in the previous example, because this ensures the cleanup code is performed even ifsuper.afterEach
throws an exception.The difference between stacking traits that extend
BeforeAndAfterEach
versus traits that implementwithFixture
is that setup and cleanup code happens before and after the test inBeforeAndAfterEach
, but at the beginning and end of the test inwithFixture
. Thus if awithFixture
method completes abruptly with an exception, it is considered a failed test. By contrast, if any of thebeforeEach
orafterEach
methods ofBeforeAndAfterEach
complete abruptly, it is considered an aborted suite, which will result in aSuiteAborted
event.Shared tests
Sometimes you may want to run the same test code on different fixture objects. In other words, you may want to write tests that are "shared" by different fixture objects. To accomplish this in an
AsyncFunSpec
, you first place shared tests in behavior functions. These behavior functions will be invoked during the construction phase of anyAsyncFunSpec
that uses them, so that the tests they contain will be registered as tests in thatAsyncFunSpec
. For example, given thisStackActor
class:package org.scalatest.examples.asyncfunspec.sharedtests
import scala.collection.mutable.ListBuffer import scala.concurrent.Future import scala.concurrent.ExecutionContext
// Stack operations case class Push[T](value: T) sealed abstract class StackOp case object Pop extends StackOp case object Peek extends StackOp case object Size extends StackOp
// Stack info case class StackInfo[T](top: Option[T], size: Int, max: Int) { require(size > 0, "size was less than zero") require(max > size, "max was less than size") val isFull: Boolean = size == max val isEmpty: Boolean = size == 0 }
class StackActor[T](Max: Int, name: String) {
private final val buf = new ListBuffer[T]
def !(push: Push[T]): Unit = synchronized { if (buf.size != Max) buf.prepend(push.value) else throw new IllegalStateException("can't push onto a full stack") }
def ?(op: StackOp)(implicit c: ExecutionContext): Future[StackInfo[T]] = synchronized { op match { case Pop => Future { if (buf.size != 0) StackInfo(Some(buf.remove(0)), buf.size, Max) else throw new IllegalStateException("can't pop an empty stack") } case Peek => Future { if (buf.size != 0) StackInfo(Some(buf(0)), buf.size, Max) else throw new IllegalStateException("can't peek an empty stack") } case Size => Future { StackInfo(None, buf.size, Max) } } }
override def toString: String = name }You may want to test the stack represented by the
StackActor
class in different states: empty, full, with one item, with one item less than capacity, etc. You may find you have several tests that make sense any time the stack is non-empty. Thus you'd ideally want to run those same tests for three stack fixture objects: a full stack, a stack with a one item, and a stack with one item less than capacity. With shared tests, you can factor these tests out into a behavior function, into which you pass the stack fixture to use when running the tests. So in yourAsyncFunSpec
forStackActor
, you'd invoke the behavior function three times, passing in each of the three stack fixtures so that the shared tests are run for all three fixtures.You can define a behavior function that encapsulates these shared tests inside the
AsyncFunSpec
that uses them. If they are shared between differentAsyncFunSpec
s, however, you could also define them in a separate trait that is mixed into eachAsyncFunSpec
that uses them. For example, here thenonEmptyStackActor
behavior function (in this case, a behavior method) is defined in a trait along with another method containing shared tests for non-full stacks:import org.scalatest.AsyncFunSpec
trait AsyncFunSpecStackBehaviors { this: AsyncFunSpec =>
def nonEmptyStackActor(createNonEmptyStackActor: => StackActor[Int], lastItemAdded: Int, name: String): Unit = {
it("should return non-empty StackInfo when Size is fired at non-empty stack actor: " + name) { val stackActor = createNonEmptyStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(!stackInfo.isEmpty) } }
it("should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: " + name) { val stackActor = createNonEmptyStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePeek <- stackActor ? Size afterPeek <- stackActor ? Peek } yield (beforePeek, afterPeek) futurePair map { case (beforePeek, afterPeek) => assert(afterPeek.top == Some(lastItemAdded)) assert(afterPeek.size == beforePeek.size) } }
it("should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: " + name) { val stackActor = createNonEmptyStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePop <- stackActor ? Size afterPop <- stackActor ? Pop } yield (beforePop, afterPop) futurePair map { case (beforePop, afterPop) => assert(afterPop.top == Some(lastItemAdded)) assert(afterPop.size == beforePop.size - 1) } } }
def nonFullStackActor(createNonFullStackActor: => StackActor[Int], name: String): Unit = {
it("should return non-full StackInfo when Size is fired at non-full stack actor: " + name) { val stackActor = createNonFullStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(!stackInfo.isFull) } }
it("should return before and after StackInfo that has existing size + 1 and new item as top when Push is fired at non-full stack actor: " + name) { val stackActor = createNonFullStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePush <- stackActor ? Size afterPush <- { stackActor ! Push(7); stackActor ? Peek } } yield (beforePush, afterPush) futurePair map { case (beforePush, afterPush) => assert(afterPush.top == Some(7)) assert(afterPush.size == beforePush.size + 1) } } } }Given these behavior functions, you could invoke them directly, but
AsyncFunSpec
offers a DSL for the purpose, which looks like this:it should behave like nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName) it should behave like nonFullStackActor(almostEmptyStackActor, almostEmptyStackActorName)
Here's an example:
class StackSpec extends AsyncFunSpec with AsyncFunSpecStackBehaviors {
val Max = 10 val LastValuePushed = Max - 1
// Stack fixture creation methods val emptyStackActorName = "empty stack actor" def emptyStackActor = new StackActor[Int](Max, emptyStackActorName )
val fullStackActorName = "full stack actor" def fullStackActor = { val stackActor = new StackActor[Int](Max, fullStackActorName ) for (i <- 0 until Max) stackActor ! Push(i) stackActor }
val almostEmptyStackActorName = "almost empty stack actor" def almostEmptyStackActor = { val stackActor = new StackActor[Int](Max, almostEmptyStackActorName ) stackActor ! Push(LastValuePushed) stackActor }
val almostFullStackActorName = "almost full stack actor" def almostFullStackActor = { val stackActor = new StackActor[Int](Max, almostFullStackActorName) for (i <- 1 to LastValuePushed) stackActor ! Push(i) stackActor }
describe("A Stack") { describe("(when empty)") { it("should be empty") { val stackActor = emptyStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(stackInfo.isEmpty) } }
it("should complain on peek") { recoverToSucceededIf[IllegalStateException] { emptyStackActor ? Peek } }
it("should complain on pop") { recoverToSucceededIf[IllegalStateException] { emptyStackActor ? Pop } } }
describe("(when non-empty)") { it should behave like nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName) it should behave like nonFullStackActor(almostEmptyStackActor, almostEmptyStackActorName) it should behave like nonEmptyStackActor(almostFullStackActor, LastValuePushed, almostFullStackActorName) it should behave like nonFullStackActor(almostFullStackActor, almostFullStackActorName) }
describe("(when full)") {
it("should be full") { val stackActor = fullStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(stackInfo.isFull) } }
it should behave like nonEmptyStackActor(fullStackActor, LastValuePushed, fullStackActorName)
it("should complain on a push") { val stackActor = fullStackActor assertThrows[IllegalStateException] { stackActor ! Push(10) } } } } }If you load these classes into the Scala interpreter (with scalatest's JAR file on the class path), and execute it, you'll see:
scala> org.scalatest.run(new StackSpec) StackSpec: A Stack (when empty) - should be empty - should complain on peek - should complain on pop (when non-empty) - should return non-empty StackInfo when Size is fired at non-empty stack actor: almost empty stack actor - should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost empty stack actor - should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost empty stack actor - should return non-full StackInfo when Size is fired at non-full stack actor: almost empty stack actor - should return before and after StackInfo that has existing size + 1 and new item as top when Push is fired at non-full stack actor: almost empty stack actor - should return non-empty StackInfo when Size is fired at non-empty stack actor: almost full stack actor - should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost full stack actor - should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost full stack actor - should return non-full StackInfo when Size is fired at non-full stack actor: almost full stack actor - should return before and after StackInfo that has existing size + 1 and new item as top when Push is fired at non-full stack actor: almost full stack actor (when full) - should be full - should return non-empty StackInfo when Size is fired at non-empty stack actor: full stack actor - should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: full stack actor - should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: full stack actor - should complain on a push
One thing to keep in mind when using shared tests is that in ScalaTest, each test in a suite must have a unique name. If you register the same tests repeatedly in the same suite, one problem you may encounter is an exception at runtime complaining that multiple tests are being registered with the same test name. Therefore, you need to do a bit of extra work to ensure that the test names are unique. If a duplicate test name problem shows up in an
AsyncFunSpec
, you'll need to pass in a prefix or suffix string to add to each test name. You can calltoString
on the shared fixture object, or pass this string the same way you pass any other data needed by the shared tests. This is the approach taken by the previousAsyncFunSpecStackBehaviors
example.Given this
AsyncFunSpecStackBehaviors
trait, calling it with thestackWithOneItem
fixture, like this:it should behave like nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName)
yields test names:
A Stack (when non-empty) should return non-empty StackInfo when Size is fired at non-empty stack actor: almost empty stack actor
A Stack (when non-empty) should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost empty stack actor
A Stack (when non-empty) should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost empty stack actor
Whereas calling it with the
stackWithOneItemLessThanCapacity
fixture, like this:it should behave like nonEmptyStackActor(almostFullStackActor, LastValuePushed, almostFullStackActorName)
yields different test names:
A Stack (when non-empty) should return non-empty StackInfo when Size is fired at non-empty stack actor: almost full stack actor
A Stack (when non-empty) should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost full stack actor
A Stack (when non-empty) should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost full stack actor
- trait AsyncFunSpecLike extends AsyncTestSuite with AsyncTestRegistration with Informing with Notifying with Alerting with Documenting
Implementation trait for class
AsyncFunSpec
, which facilitates a “behavior-driven” style of development (BDD), in which tests are combined with text that specifies the behavior the tests verify.Implementation trait for class
AsyncFunSpec
, which facilitates a “behavior-driven” style of development (BDD), in which tests are combined with text that specifies the behavior the tests verify.AsyncFunSpec
is a class, not a trait, to minimize compile time given there is a slight compiler overhead to mixing in traits compared to extending classes. If you need to mix the behavior ofAsyncFunSpec
into some other class, you can use this trait instead, because classAsyncFunSpec
does nothing more than extend this trait and add a nicetoString
implementation.See the documentation of the class for a detailed overview of
AsyncFunSpec
. - abstract class AsyncFunSuite extends AsyncFunSuiteLike
Enables testing of asynchronous code without blocking, using a style consistent with traditional
FunSuite
tests.Enables testing of asynchronous code without blocking, using a style consistent with traditional
FunSuite
tests.Recommended Usage: AsyncFunSuite
is intended to enable users ofFunSuite
to write non-blocking asynchronous tests that are consistent with their traditionalFunSuite
tests. Note:AsyncFunSuite
is intended for use in special situations where non-blocking asynchronous testing is needed, with classFunSuite
used for general needs.Given a
Future
returned by the code you are testing, you need not block until theFuture
completes before performing assertions against its value. You can instead map those assertions onto theFuture
and return the resultingFuture[Assertion]
to ScalaTest. The test will complete asynchronously, when theFuture[Assertion]
completes.Here's an example
AsyncFunSuite
:package org.scalatest.examples.asyncfunsuite
import org.scalatest.AsyncFunSuite import scala.concurrent.Future
class AddSuite extends AsyncFunSuite {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
test("addSoon will eventually compute a sum of passed Ints") { 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
test("addNow will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) // You can also write synchronous tests, which // must result in type Assertion: assert(sum == 3) } }“
test
” is a method, defined inAsyncFunSuite
, which will be invoked by the primary constructor ofAddSuite
. You specify the name of the test as a string between the parentheses, and the test code itself between curly braces. The test code is a function passed as a by-name parameter totest
, which registers it for later execution. The result type of the by-name in anAsyncFunSuite
must beFuture[Assertion]
.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, isFuture[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
AddSuite
is constructed, the second test will be implicitly converted toFuture[Assertion]
and registered. The implicit conversion is fromAssertion
toFuture[Assertion]
, so you must end synchronous tests in some ScalaTest assertion or matcher expression. If a test would not otherwise end in typeAssertion
, you can placesucceed
at the end of the test.succeed
, a field in traitAssertions
, returns theSucceeded
singleton:scala> succeed res2: org.scalatest.Assertion = Succeeded
Thus placing
succeed
at the end of a test body will satisfy the type checker:test("addNow will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) assert(sum == 3) println("hi") // println has result type Unit succeed // succeed has result type Assertion }
An
AsyncFunSuite
's lifecycle has two phases: the registration phase and the ready phase. It starts in registration phase and enters ready phase the first timerun
is called on it. It then remains in ready phase for the remainder of its lifetime.Tests can only be registered with the
test
method while theAsyncFunSuite
is in its registration phase. Any attempt to register a test after theAsyncFunSuite
has entered its ready phase, i.e., afterrun
has been invoked on theAsyncFunSuite
, will be met with a thrownTestRegistrationClosedException
. The recommended style of usingAsyncFunSuite
is to register tests during object construction as is done in all the examples shown here. If you keep to the recommended style, you should never see aTestRegistrationClosedException
.Asynchronous execution model
AsyncFunSuite
extendsAsyncTestSuite
, which provides an implicitscala.concurrent.ExecutionContext
namedexecutionContext
. This execution context is used byAsyncFunSuite
to transform theFuture[Assertion]
s returned by each test into theFutureOutcome
returned by thetest
function passed towithFixture
. ThisExecutionContext
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 theFutureOutcome
returned from thetest
function. Other tasks that will be queued are any transformations of, or callbacks registered on,Future
s that occur in your test body, including any assertions you map ontoFuture
s. 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 ofFuture
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]
. ThisFuture[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 theFuture[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
FunSuite
withScalaFutures
instead. Alternatively, you could override theexecutionContext
and use a traditionalExecutionContext
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 therunNow
execution context on Scala.js instead of the defaultqueue
, you would write:// on Scala.js implicit override def executionContext = org.scalatest.concurrent.TestExecutionContext.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 anAsyncFunSuite
will be executed one after another, i.e., serially. This is true whether those tests returnAssertion
orFuture[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
AsyncFunSuite
to be executed in parallel, you must mix inParallelTestExecution
and enable parallel execution of tests in your build. You enable parallel execution inRunner
with the-P
command line flag. In the ScalaTest Maven Plugin, setparallel
totrue
. Insbt
, 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 theDistributor
, and allowed to complete in parallel, using threads from theexecutionContext
. If you are using ScalaTest's serial execution context, the JVM default, asynchronous tests will run in parallel very much like traditional (such asFunSuite
) tests run in parallel: 1) BecauseParallelTestExecution
extendsOneInstancePerTest
, 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 ofFuture
s 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 invokedrun
, 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 theexecutionContext
. 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 inParallelTestExecution
is executed: the tests will run sequentially. If you use an execution context backed by a thread-pool, such asglobal
, 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
andrecoverToExceptionIf
methods of traitRecoverMethods
. Because this trait is mixed into supertraitAsyncTestSuite
, both of these methods are available by default in anAsyncFunSuite
.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 toassertThrows
, except in the context of a future. It transforms aFuture
of any type into aFuture[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 therecoverToSucceededIf
in its behavior when the assertion succeeds:recoverToSucceededIf
yields aFuture[Assertion]
, whereasrecoverToExceptionIf
yields aFuture[T]
, whereT
is the expected exception type.recoverToExceptionIf[IllegalStateException] { // Result type: Future[IllegalStateException] emptyStackActor ? Peek }
In other words,
recoverToExpectionIf
is tointercept
asrecovertToSucceededIf
is toassertThrows
. 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 showingrecoverToExceptionIf
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]"))
Ignored tests
To support the common use case of temporarily disabling a test, with the good intention of resurrecting the test at a later time,
AsyncFunSuite
provides registration methods that start withignore
instead oftest
. Here's an example:package org.scalatest.examples.asyncfunsuite.ignore
import org.scalatest.AsyncFunSuite import scala.concurrent.Future
class AddSuite extends AsyncFunSuite {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
ignore("addSoon will eventually compute a sum of passed Ints") { 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
test("addNow will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } }If you run this version of
AddSuite
with:scala> org.scalatest.run(new AddSuite)
It will run only the second test and report that the first test was ignored:
AddSuite: - addSoon will eventually compute a sum of passed Ints !!! IGNORED !!! - addNow will immediately compute a sum of passed Ints
If you wish to temporarily ignore an entire suite of tests, you can (on the JVM, not Scala.js) annotate the test class with
@Ignore
, like this:package org.scalatest.examples.asyncfunsuite.ignoreall
import org.scalatest.AsyncFunSuite import scala.concurrent.Future import org.scalatest.Ignore
@Ignore class AddSuite extends AsyncFunSuite {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
test("addSoon will eventually compute a sum of passed Ints") { 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
test("addNow will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } }When you mark a test class with a tag annotation, ScalaTest will mark each test defined in that class with that tag. Thus, marking the
AddSuite
in the above example with the@Ignore
tag annotation means that both tests in the class will be ignored. If you run the aboveAddSuite
in the Scala interpreter, you'll see:scala> org.scalatest.run(new AddSuite) AddSuite: - addSoon will eventually compute a sum of passed Ints !!! IGNORED !!! - addNow will immediately compute a sum of passed Ints !!! IGNORED !!!
Note that marking a test class as ignored won't prevent it from being discovered by ScalaTest. Ignored classes will be discovered and run, and all their tests will be reported as ignored. This is intended to keep the ignored class visible, to encourage the developers to eventually fix and “un-ignore” it. If you want to prevent a class from being discovered at all (on the JVM, not Scala.js), use the
DoNotDiscover
annotation instead.If you want to ignore all tests of a suite on Scala.js, where annotations can't be inspected at runtime, you'll need to change
test
toignore
at each test site. To make a suite non-discoverable on Scala.js, ensure it does not declare a public no-arg constructor. You can either declare a public constructor that takes one or more arguments, or make the no-arg constructor non-public. Because this technique will also make the suite non-discoverable on the JVM, it is a good approach for suites you want to run (but not be discoverable) on both Scala.js and the JVM.Informers
One of the parameters to
AsyncFunSuite
'srun
method is aReporter
, which will collect and report information about the running suite of tests. Information about suites and tests that were run, whether tests succeeded or failed, and tests that were ignored will be passed to theReporter
as the suite runs. Most often the reporting done by default byAsyncFunSuite
's methods will be sufficient, but occasionally you may wish to provide custom information to theReporter
from a test. For this purpose, anInformer
that will forward information to the currentReporter
is provided via theinfo
parameterless method. You can pass the extra information to theInformer
via itsapply
method. TheInformer
will then pass the information to theReporter
via anInfoProvided
event. Here's an example that shows both a direct use as well as an indirect use through the methods ofGivenWhenThen
:package org.scalatest.examples.asyncfunsuite.info
import collection.mutable import org.scalatest._
class SetSuite extends AsyncFunSuite with GivenWhenThen {
test("An element can be added to an empty mutable Set") {
Given("an empty mutable Set") val set = mutable.Set.empty[String]
When("an element is added") set += "clarity"
Then("the Set should have size 1") assert(set.size === 1)
And("the Set should contain the added element") assert(set.contains("clarity"))
info("That's all folks!") succeed } }If you run this
AsyncFunSuite
from the interpreter, you will see the following output:scala> org.scalatest.run(new SetSuite) SetSuite: - an element can be added to an empty mutable Set + Given an empty mutable Set + When an element is added + Then the Set should have size 1 + And the Set should contain the added element + That's all folks!
Documenters
AsyncFunSuite
also provides amarkup
method that returns aDocumenter
, which allows you to send to theReporter
text formatted in Markdown syntax. You can pass the extra information to theDocumenter
via itsapply
method. TheDocumenter
will then pass the information to theReporter
via anMarkupProvided
event.Here's an example
AsyncFunSuite
that usesmarkup
:package org.scalatest.examples.asyncfunsuite.markup
import collection.mutable import org.scalatest._
class SetSuite extends AsyncFunSuite with GivenWhenThen {
markup { """ Mutable Set ———-- A set is a collection that contains no duplicate elements. To implement a concrete mutable set, you need to provide implementations of the following methods: def contains(elem: A): Boolean def iterator: Iterator[A] def += (elem: A): this.type def -= (elem: A): this.type If you wish that methods like `take`, `drop`, `filter` return the same kind of set, you should also override: def empty: This It is also good idea to override methods `foreach` and `size` for efficiency. """ }
test("An element can be added to an empty mutable Set") {
Given("an empty mutable Set") val set = mutable.Set.empty[String]
When("an element is added") set += "clarity"
Then("the Set should have size 1") assert(set.size === 1)
And("the Set should contain the added element") assert(set.contains("clarity"))
markup("This test finished with a **bold** statement!") succeed } }Although all of ScalaTest's built-in reporters will display the markup text in some form, the HTML reporter will format the markup information into HTML. Thus, the main purpose of
markup
is to add nicely formatted text to HTML reports. Here's what the aboveSetSpec
would look like in the HTML reporter:Notifiers and alerters
ScalaTest records text passed to
info
andmarkup
during tests, and sends the recorded text in therecordedEvents
field of test completion events likeTestSucceeded
andTestFailed
. This allows string reporters (like the standard out reporter) to showinfo
andmarkup
text after the test name in a color determined by the outcome of the test. For example, if the test fails, string reporters will show theinfo
andmarkup
text in red. If a test succeeds, string reporters will show theinfo
andmarkup
text in green. While this approach helps the readability of reports, it means that you can't useinfo
to get status updates from long running tests.To get immediate (i.e., non-recorded) notifications from tests, you can use
note
(aNotifier
) andalert
(anAlerter
). Here's an example showing the differences:package org.scalatest.examples.asyncfunsuite.note
import collection.mutable import org.scalatest._
class SetSuite extends AsyncFunSuite {
test("An element can be added to an empty mutable Set") {
info("info is recorded") markup("markup is *also* recorded") note("notes are sent immediately") alert("alerts are also sent immediately")
val set = mutable.Set.empty[String] set += "clarity" assert(set.size === 1) assert(set.contains("clarity")) } }Because
note
andalert
information is sent immediately, it will appear before the test name in string reporters, and its color will be unrelated to the ultimate outcome of the test:note
text will always appear in green,alert
text will always appear in yellow. Here's an example:scala> org.scalatest.run(new SetSpec) SetSuite: + notes are sent immediately + alerts are also sent immediately - An element can be added to an empty mutable Set + info is recorded + markup is *also* recorded
Another example is slowpoke notifications. If you find a test is taking a long time to complete, but you're not sure which test, you can enable slowpoke notifications. ScalaTest will use an
Alerter
to fire an event whenever a test has been running longer than a specified amount of time.In summary, use
info
andmarkup
for text that should form part of the specification output. Usenote
andalert
to send status notifications. (Because the HTML reporter is intended to produce a readable, printable specification,info
andmarkup
text will appear in the HTML report, butnote
andalert
text will not.)Pending tests
A pending test is one that has been given a name but is not yet implemented. The purpose of pending tests is to facilitate a style of testing in which documentation of behavior is sketched out before tests are written to verify that behavior (and often, before the behavior of the system being tested is itself implemented). Such sketches form a kind of specification of what tests and functionality to implement later.
To support this style of testing, a test can be given a name that specifies one bit of behavior required by the system being tested. At the end of the test, it can call method
pending
, which will cause it to complete abruptly withTestPendingException
.Because tests in ScalaTest can be designated as pending with
TestPendingException
, both the test name and any information sent to the reporter when running the test can appear in the report of a test run. (In other words, the code of a pending test is executed just like any other test.) However, because the test completes abruptly withTestPendingException
, the test will be reported as pending, to indicate the actual test, and possibly the functionality, has not yet been implemented. Here's an example:package org.scalatest.examples.asyncfunsuite.pending
import org.scalatest.AsyncFunSuite import scala.concurrent.Future
class AddSuite extends AsyncFunSuite {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
test("addSoon will eventually compute a sum of passed Ints") (pending)
def addNow(addends: Int*): Int = addends.sum
test("addNow will immediately compute a sum of passed Ints") { val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } }(Note: "
(pending)
" is the body of the test. Thus the test contains just one statement, an invocation of thepending
method, which throwsTestPendingException
.) If you run this version ofAddSuite
with:scala> org.scalatest.run(new AddSuite)
It will run both tests, but report that first test is pending. You'll see:
AddSuite: - addSoon will eventually compute a sum of passed Ints (pending) - addNow will immediately compute a sum of passed Ints
One difference between an ignored test and a pending one is that an ignored test is intended to be used during significant refactorings of the code under test, when tests break and you don't want to spend the time to fix all of them immediately. You can mark some of those broken tests as ignored temporarily, so that you can focus the red bar on just failing tests you actually want to fix immediately. Later you can go back and fix the ignored tests. In other words, by ignoring some failing tests temporarily, you can more easily notice failed tests that you actually want to fix. By contrast, a pending test is intended to be used before a test and/or the code under test is written. Pending indicates you've decided to write a test for a bit of behavior, but either you haven't written the test yet, or have only written part of it, or perhaps you've written the test but don't want to implement the behavior it tests until after you've implemented a different bit of behavior you realized you need first. Thus ignored tests are designed to facilitate refactoring of existing code whereas pending tests are designed to facilitate the creation of new code.
One other difference between ignored and pending tests is that ignored tests are implemented as a test tag that is excluded by default. Thus an ignored test is never executed. By contrast, a pending test is implemented as a test that throws
TestPendingException
(which is what calling thepending
method does). Thus the body of pending tests are executed up until they throwTestPendingException
.Tagging tests
An
AsyncFunSuite
's tests may be classified into groups by tagging them with string names. As with any suite, when executing anAsyncFunSuite
, groups of tests can optionally be included and/or excluded. To tag anAsyncFunSuite
's tests, you pass objects that extend classorg.scalatest.Tag
to methods that register tests. ClassTag
takes one parameter, a string name. If you have created tag annotation interfaces as described in theTag
documentation, then you will probably want to use tag names on your test functions that match. To do so, simply pass the fully qualified names of the tag interfaces to theTag
constructor. For example, if you've defined a tag annotation interface with fully qualified name,com.mycompany.tags.DbTest
, then you could create a matching tag forAsyncFunSuite
s like this:package org.scalatest.examples.asyncfunsuite.tagging
import org.scalatest.Tag
object DbTest extends Tag("com.mycompany.tags.DbTest")Given these definitions, you could place
AsyncFunSuite
tests into groups with tags like this:import org.scalatest.AsyncFunSuite import org.scalatest.tagobjects.Slow import scala.concurrent.Future
class AddSuite extends AsyncFunSuite {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
test("addSoon will eventually compute a sum of passed Ints", Slow) { 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
test("addNow will immediately compute a sum of passed Ints", Slow, DbTest) {
val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } }This code marks both tests with the
org.scalatest.tags.Slow
tag, and the second test with thecom.mycompany.tags.DbTest
tag.The
run
method takes aFilter
, whose constructor takes an optionalSet[String]
calledtagsToInclude
and aSet[String]
calledtagsToExclude
. IftagsToInclude
isNone
, all tests will be run except those those belonging to tags listed in thetagsToExclude
Set
. IftagsToInclude
is defined, only tests belonging to tags mentioned in thetagsToInclude
set, and not mentioned intagsToExclude
, will be run.It is recommended, though not required, that you create a corresponding tag annotation when you create a
Tag
object. A tag annotation (on the JVM, not Scala.js) allows you to tag all the tests of anAsyncFunSuite
in one stroke by annotating the class. For more information and examples, see the documentation for classTag
. On Scala.js, to tag all tests of a suite, you'll need to tag each test individually at the test site.Shared fixtures
A test fixture is composed of the objects and other artifacts (files, sockets, database connections, etc.) tests use to do their work. When multiple tests need to work with the same fixtures, it is important to try and avoid duplicating the fixture code across those tests. The more code duplication you have in your tests, the greater drag the tests will have on refactoring the actual production code.
ScalaTest recommends three techniques to eliminate such code duplication in async styles:
- Refactor using Scala
- Override
withFixture
- Mix in a before-and-after trait
Each technique is geared towards helping you reduce code duplication without introducing instance
var
s, shared mutable objects, or other dependencies between tests. Eliminating shared mutable state across tests will make your test code easier to reason about and eliminate the need to synchronize access to shared mutable state on the JVM.The following sections describe these techniques, including explaining the recommended usage for each. But first, here's a table summarizing the options:
Refactor using Scala when different tests need different fixtures. get-fixture methods The extract method refactor helps you create a fresh instances of mutable fixture objects in each test that needs them, but doesn't help you clean them up when you're done. loan-fixture methods Factor out dupicate code with the loan pattern when different tests need different fixtures that must be cleaned up afterwards. Override withFixture
when most or all tests need the same fixture.withFixture(NoArgAsyncTest)
The recommended default approach when most or all tests need the same fixture treatment. This general technique allows you, for example, to perform side effects at the beginning and end of all or most tests, transform the outcome of tests, retry tests, make decisions based on test names, tags, or other test data. Use this technique unless: - Different tests need different fixtures (refactor using Scala instead)
- An exception in fixture code should abort the suite, not fail the test (use a before-and-after trait instead)
- You have objects to pass into tests (override
withFixture(OneArgAsyncTest)
instead)
withFixture(OneArgAsyncTest)
Use when you want to pass the same fixture object or objects as a parameter into all or most tests. Mix in a before-and-after trait when you want an aborted suite, not a failed test, if the fixture code fails. BeforeAndAfter
Use this boilerplate-buster when you need to perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. BeforeAndAfterEach
Use when you want to stack traits that perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. Calling get-fixture methods
If you need to create the same mutable fixture objects in multiple tests, and don't need to clean them up after using them, the simplest approach is to write one or more get-fixture methods. A get-fixture method returns a new instance of a needed fixture object (or a holder object containing multiple fixture objects) each time it is called. You can call a get-fixture method at the beginning of each test that needs the fixture, storing the returned object or objects in local variables. Here's an example:
package org.scalatest.examples.asyncfunsuite.getfixture
import org.scalatest.AsyncFunSuite import collection.mutable.ListBuffer import scala.concurrent.Future
class ExampleSuite extends AsyncFunSuite {
def fixture: Future[String] = Future { "ScalaTest is " }
test("Testing should be easy") { val future = fixture val result = future map { s => s + "easy!" } result map { s => assert(s === "ScalaTest is easy!") } }
test("Testing should be fun") { val future = fixture val result = future map { s => s + "fun!" } result map { s => assert(s === "ScalaTest is fun!") } } }If you need to configure fixture objects differently in different tests, you can pass configuration into the get-fixture method. For example, you could pass in an initial value for a fixture object as a parameter to the get-fixture method.
Overriding
withFixture(NoArgAsyncTest)
Although the get-fixture method approach takes care of setting up a fixture at the beginning of each test, it doesn't address the problem of cleaning up a fixture at the end of the test. If you just need to perform a side-effect at the beginning or end of a test, and don't need to actually pass any fixture objects into the test, you can override
withFixture(NoArgAsyncTest)
, a method defined in traitAsyncTestSuite
, a supertrait ofAsyncFunSuite
.Trait
AsyncFunSuite
'srunTest
method passes a no-arg async test function towithFixture(NoArgAsyncTest)
. It iswithFixture
's responsibility to invoke that test function. The default implementation ofwithFixture
simply invokes the function and returns the result, like this:// 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 thecomplete
-lastly
syntax, defined in supertraitCompleteLastly
. Thecomplete
-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 thesuper
implementation ofwithFixture
, 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 ofFuture
's registration methods:onComplete
,onSuccess
, oronFailure
. Note that if a test fails, that will be treated as ascala.util.Success(org.scalatest.Failed)
. So if you want to perform an action if a test fails, for example, you'd register the callback usingonSuccess
.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.asyncfunsuite.noargasynctest
import java.io.File import org.scalatest._ import scala.concurrent.Future
class ExampleSuite extends AsyncFunSuite {
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 }
test("This test should succeed") { addSoon(1, 1) map { sum => assert(sum === 2) } }
test("This test should fail") { addSoon(1, 1) map { sum => assert(sum === 3) } } }Running this version of
ExampleSuite
in the interpreter in a directory with two files,hello.txt
andworld.txt
would give the following output:scala> org.scalatest.run(new ExampleSuite) ExampleSuite: - this test should succeed Dir snapshot: hello.txt, world.txt - this test should fail *** FAILED *** 2 did not equal 3 (
:33) Note that the
NoArgAsyncTest
passed towithFixture
, in addition to anapply
method that executes the test, also includes the test name and the config map passed torunTest
. Thus you can also use the test name and configuration objects in yourwithFixture
implementation.Lastly, if you want to transform the outcome in some way in
withFixture
, you'll need to use either themap
ortransform
methods ofFuture
, 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
'sapply
method will return ascala.util.Failure
only if the test completes abruptly with a "test-fatal" exception (such asOutOfMemoryError
) that should cause the suite to abort rather than the test to fail. Thus usually you would usemap
to transform future outcomes, nottransform
, so that such test-fatal exceptions pass through unchanged. The suite will abort asynchronously with any exception returned fromNoArgAsyncTest
's apply method in ascala.util.Failure
.Calling loan-fixture methods
If you need to both pass a fixture object into a test and perform cleanup at the end of the test, you'll need to use the loan pattern. If different tests need different fixtures that require cleanup, you can implement the loan pattern directly by writing loan-fixture methods. A loan-fixture method takes a function whose body forms part or all of a test's code. It creates a fixture, passes it to the test code by invoking the function, then cleans up the fixture after the function returns.
The following example shows three tests that use two fixtures, a database and a file. Both require cleanup after, so each is provided via a loan-fixture method. (In this example, the database is simulated with a
StringBuffer
.)package org.scalatest.examples.asyncfunsuite.loanfixture
import java.util.concurrent.ConcurrentHashMap
import scala.concurrent.Future import scala.concurrent.ExecutionContext
object DbServer { // Simulating a database server type Db = StringBuffer private final val databases = new ConcurrentHashMap[String, Db] def createDb(name: String): Db = { val db = new StringBuffer // java.lang.StringBuffer is thread-safe databases.put(name, db) db } def removeDb(name: String): Unit = { databases.remove(name) } }
// 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 } } }
import org.scalatest._ import DbServer._ import java.util.UUID.randomUUID
class ExampleSuite extends AsyncFunSuite {
def withDatabase(testCode: Future[Db] => Future[Assertion]) = { val dbName = randomUUID.toString // generate a unique db name val futureDb = Future { createDb(dbName) } // create the fixture complete { val futurePopulatedDb = futureDb map { db => db.append("ScalaTest is ") // perform setup } testCode(futurePopulatedDb) // "loan" the fixture to the test code } lastly { removeDb(dbName) // ensure the fixture will be cleaned up } }
def withActor(testCode: StringActor => Future[Assertion]) = { val actor = new StringActor complete { actor ! Append("ScalaTest is ") // set up the fixture testCode(actor) // "loan" the fixture to the test code } lastly { actor ! Clear // ensure the fixture will be cleaned up } }
// This test needs the actor fixture test("Testing should be productive") { withActor { actor => actor ! Append("productive!") val futureString = actor ? GetValue futureString map { s => assert(s === "ScalaTest is productive!") } } }
// This test needs the database fixture test("Test code should be readable") { withDatabase { futureDb => futureDb map { db => db.append("readable!") assert(db.toString === "ScalaTest is readable!") } } }
// This test needs both the actor and the database test("Test code should be clear and concise") { withDatabase { futureDb => withActor { actor => // loan-fixture methods compose actor ! Append("concise!") val futureString = actor ? GetValue val futurePair: Future[(Db, String)] = futureDb zip futureString futurePair map { case (db, s) => db.append("clear!") assert(db.toString === "ScalaTest is clear!") assert(s === "ScalaTest is concise!") } } } } }As demonstrated by the last test, loan-fixture methods compose. Not only do loan-fixture methods allow you to give each test the fixture it needs, they allow you to give a test multiple fixtures and clean everything up afterwards.
Also demonstrated in this example is the technique of giving each test its own "fixture sandbox" to play in. When your fixtures involve external side-effects, like creating databases, it is a good idea to give each database a unique name as is done in this example. This keeps tests completely isolated, allowing you to run them in parallel if desired.
Overriding
withFixture(OneArgTest)
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 overridingwithFixture(OneArgAsyncTest)
. Each test in afixture.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 specifyingFixtureParam
, and implement awithFixture
method that takes aOneArgAsyncTest
. ThiswithFixture
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 letwithFixture(NoArgAsyncTest)
invoke the test function instead of invoking the test function directly. To do so, you'll need to convert theOneArgAsyncTest
to aNoArgAsyncTest
. You can do that by passing the fixture object to thetoNoArgAsyncTest
method ofOneArgAsyncTest
. In other words, instead of writing “test(theFixture)
”, you'd delegate responsibility for invoking the test function to thewithFixture(NoArgAsyncTest)
method of the same instance by writing:withFixture(test.toNoArgAsyncTest(theFixture))
Here's a complete example:
package org.scalatest.examples.asyncfunsuite.oneargasynctest
import org.scalatest._ import java.io._ 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 ExampleSuite extends fixture.AsyncFunSuite {
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 } }
test("Testing should be easy") { actor => actor ! Append("easy!") val futureString = actor ? GetValue futureString map { s => assert(s === "ScalaTest is easy!") } }
test("Testing should be fun") { actor => actor ! Append("fun!") val futureString = actor ? GetValue futureString map { s => assert(s === "ScalaTest is fun!") } } }In this example, the tests required one fixture object, a
StringActor
. If your tests need multiple fixture objects, you can simply define theFixtureParam
type to be a tuple containing the objects or, alternatively, a case class containing the objects. For more information on thewithFixture(OneArgAsyncTest)
technique, see the documentation forfixture.AsyncFunSuite
.Mixing in
BeforeAndAfter
In all the shared fixture examples shown so far, the activities of creating, setting up, and cleaning up the fixture objects have been performed during the test. This means that if an exception occurs during any of these activities, it will be reported as a test failure. Sometimes, however, you may want setup to happen before the test starts, and cleanup after the test has completed, so that if an exception occurs during setup or cleanup, the entire suite aborts and no more tests are attempted. The simplest way to accomplish this in ScalaTest is to mix in trait
BeforeAndAfter
. With this trait you can denote a bit of code to run before each test withbefore
and/or after each test each test withafter
, like this:package org.scalatest.examples.asyncfunsuite.beforeandafter
import org.scalatest.AsyncFunSuite import org.scalatest.BeforeAndAfter import collection.mutable.ListBuffer 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 ExampleSuite extends AsyncFunSuite with BeforeAndAfter {
final val actor = new StringActor
before { actor ! Append("ScalaTest is ") // set up the fixture }
after { actor ! Clear // clean up the fixture }
test("testing should be easy") { actor ! Append("easy!") val futureString = actor ? GetValue futureString map { s => assert(s === "ScalaTest is easy!") } }
test("testing should be fun") { actor ! Append("fun!") val futureString = actor ? GetValue futureString map { s => assert(s === "ScalaTest is fun!") } } }Note that the only way
before
andafter
code can communicate with test code is via some side-effecting mechanism, commonly by reassigning instancevar
s or by changing the state of mutable objects held from instanceval
s (as in this example). If using instancevar
s or mutable objects held from instanceval
s you wouldn't be able to run tests in parallel in the same instance of the test class (on the JVM, not Scala.js) unless you synchronized access to the shared, mutable state.Note that on the JVM, if you override ScalaTest's default serial execution context, you will likely need to worry about synchronizing access to shared mutable fixture state, because the execution context may assign different threads to process different
Future
transformations. Although access to mutable state along the same linear chain ofFuture
transformations need not be synchronized, it can be difficult to spot cases where these constraints are violated. The best approach is to use only immutable objects when transformingFuture
s. When that's not practical, involve only thread-safe mutable objects, as is done in the above example. On Scala.js, by contrast, you need not worry about thread synchronization, because in effect only one thread exists.Although
BeforeAndAfter
provides a minimal-boilerplate way to execute code before and after tests, it isn't designed to enable stackable traits, because the order of execution would be non-obvious. If you want to factor out before and after code that is common to multiple test suites, you should use traitBeforeAndAfterEach
instead, as shown later in the next section, composing fixtures by stacking traits.Composing fixtures by stacking traits
In larger projects, teams often end up with several different fixtures that test classes need in different combinations, and possibly initialized (and cleaned up) in different orders. A good way to accomplish this in ScalaTest is to factor the individual fixtures into traits that can be composed using the stackable trait pattern. This can be done, for example, by placing
withFixture
methods in several traits, each of which callsuper.withFixture
. Here's an example in which theStringBuilderActor
andStringBufferActor
fixtures used in the previous examples have been factored out into two stackable fixture traits namedBuilder
andBuffer
:package org.scalatest.examples.asyncfunsuite.composingwithasyncfixture
import org.scalatest._ import org.scalatest.SuiteMixin import collection.mutable.ListBuffer 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 StringBuilderActor { // 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 StringBufferActor { private final val buf = ListBuffer.empty[String] def !(op: StringOp): Unit = synchronized { op match { case Append(value) => buf += value case Clear => buf.clear() } } def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[List[String]] = Future { synchronized { buf.toList } } }
trait Builder extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val builderActor = new StringBuilderActor
abstract override def withFixture(test: NoArgAsyncTest) = { builderActor ! Append("ScalaTest is ") complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { builderActor ! Clear } } }
trait Buffer extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val bufferActor = new StringBufferActor
abstract override def withFixture(test: NoArgAsyncTest) = { complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { bufferActor ! Clear } } }
class ExampleSuite extends AsyncFunSuite with Builder with Buffer {
test("Testing should be easy") { builderActor ! Append("easy!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str === "ScalaTest is easy!") assert(lst.isEmpty) bufferActor ! Append("sweet") succeed } }
test("Testing should be fun") { builderActor ! Append("fun!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str === "ScalaTest is fun!") assert(lst.isEmpty) bufferActor ! Append("awesome") succeed } } }By mixing in both the
Builder
andBuffer
traits,ExampleSuite
gets both fixtures, which will be initialized before each test and cleaned up after. The order the traits are mixed together determines the order of execution. In this case,Builder
is “super” toBuffer
. If you wantedBuffer
to be “super” toBuilder
, you need only switch the order you mix them together, like this:class Example2Suite extends AsyncFunSuite with Buffer with Builder
If you only need one fixture you mix in only that trait:
class Example3Suite extends AsyncFunSuite with Builder
Another way to create stackable fixture traits is by extending the
BeforeAndAfterEach
and/orBeforeAndAfterAll
traits.BeforeAndAfterEach
has abeforeEach
method that will be run before each test (like JUnit'ssetUp
), and anafterEach
method that will be run after (like JUnit'stearDown
). Similarly,BeforeAndAfterAll
has abeforeAll
method that will be run before all tests, and anafterAll
method that will be run after all tests. Here's what the previously shown example would look like if it were rewritten to use theBeforeAndAfterEach
methods instead ofwithFixture
:package org.scalatest.examples.asyncfunsuite.composingbeforeandaftereach
import org.scalatest._ import org.scalatest.BeforeAndAfterEach import collection.mutable.ListBuffer 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 StringBuilderActor { // 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 StringBufferActor { private final val buf = ListBuffer.empty[String] def !(op: StringOp): Unit = synchronized { op match { case Append(value) => buf += value case Clear => buf.clear() } } def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[List[String]] = Future { synchronized { buf.toList } } }
trait Builder extends BeforeAndAfterEach { this: Suite =>
final val builderActor = new StringBuilderActor
override def beforeEach() { builderActor ! Append("ScalaTest is ") super.beforeEach() // To be stackable, must call super.beforeEach }
override def afterEach() { try super.afterEach() // To be stackable, must call super.afterEach finally builderActor ! Clear } }
trait Buffer extends BeforeAndAfterEach { this: Suite =>
final val bufferActor = new StringBufferActor
override def afterEach() { try super.afterEach() // To be stackable, must call super.afterEach finally bufferActor ! Clear } }
class ExampleSuite extends AsyncFunSuite with Builder with Buffer {
test("Testing should be easy") { builderActor ! Append("easy!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str === "ScalaTest is easy!") assert(lst.isEmpty) bufferActor ! Append("sweet") succeed } }
test("Testing should be fun") { builderActor ! Append("fun!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str === "ScalaTest is fun!") assert(lst.isEmpty) bufferActor ! Append("awesome") succeed } } }To get the same ordering as
withFixture
, place yoursuper.beforeEach
call at the end of eachbeforeEach
method, and thesuper.afterEach
call at the beginning of eachafterEach
method, as shown in the previous example. It is a good idea to invokesuper.afterEach
in atry
block and perform cleanup in afinally
clause, as shown in the previous example, because this ensures the cleanup code is performed even ifsuper.afterEach
throws an exception.The difference between stacking traits that extend
BeforeAndAfterEach
versus traits that implementwithFixture
is that setup and cleanup code happens before and after the test inBeforeAndAfterEach
, but at the beginning and end of the test inwithFixture
. Thus if awithFixture
method completes abruptly with an exception, it is considered a failed test. By contrast, if any of thebeforeEach
orafterEach
methods ofBeforeAndAfterEach
complete abruptly, it is considered an aborted suite, which will result in aSuiteAborted
event.Shared tests
Sometimes you may want to run the same test code on different fixture objects. In other words, you may want to write tests that are "shared" by different fixture objects. To accomplish this in an
AsyncFunSuite
, you first place shared tests in behavior functions. These behavior functions will be invoked during the construction phase of anyAsyncFunSuite
that uses them, so that the tests they contain will be registered as tests in thatAsyncFunSuite
. For example, given thisStackActor
class:package org.scalatest.examples.asyncfunsuite.sharedtests
import scala.collection.mutable.ListBuffer import scala.concurrent.Future import scala.concurrent.ExecutionContext
// Stack operations case class Push[T](value: T) sealed abstract class StackOp case object Pop extends StackOp case object Peek extends StackOp case object Size extends StackOp
// Stack info case class StackInfo[T](top: Option[T], size: Int, max: Int) { require(size >= 0, "size was less than zero") require(max >= size, "max was less than size") val isFull: Boolean = size == max val isEmpty: Boolean = size == 0 }
class StackActor[T](Max: Int, name: String) {
private final val buf = new ListBuffer[T]
def !(push: Push[T]): Unit = synchronized { if (buf.size != Max) buf.prepend(push.value) else throw new IllegalStateException("can't push onto a full stack") }
def ?(op: StackOp)(implicit c: ExecutionContext): Future[StackInfo[T]] = synchronized { op match { case Pop => if (buf.size != 0) Future { StackInfo(Some(buf.remove(0)), buf.size, Max) } else throw new IllegalStateException("can't pop an empty stack") case Peek => if (buf.size != 0) Future { StackInfo(Some(buf(0)), buf.size, Max) } else throw new IllegalStateException("can't peek an empty stack") case Size => Future { StackInfo(None, buf.size, Max) } } }
override def toString: String = name }You may want to test the stack represented by the
StackActor
class in different states: empty, full, with one item, with one item less than capacity, etc. You may find you have several tests that make sense any time the stack is non-empty. Thus you'd ideally want to run those same tests for three stack fixture objects: a full stack, a stack with a one item, and a stack with one item less than capacity. With shared tests, you can factor these tests out into a behavior function, into which you pass the stack fixture to use when running the tests. So in yourAsyncFunSuite
forStackActor
, you'd invoke the behavior function three times, passing in each of the three stack fixtures so that the shared tests are run for all three fixtures.You can define a behavior function that encapsulates these shared tests inside the
AsyncFunSuite
that uses them. If they are shared between differentAsyncFunSuite
s, however, you could also define them in a separate trait that is mixed into eachAsyncFunSuite
that uses them. For example, here thenonEmptyStackActor
behavior function (in this case, a behavior method) is defined in a trait along with another method containing shared tests for non-full stacks:import org.scalatest.AsyncFunSuite
trait AsyncFunSuiteStackBehaviors { this: AsyncFunSuite =>
def nonEmptyStackActor(createNonEmptyStackActor: => StackActor[Int], lastItemAdded: Int, name: String): Unit = {
test("Size is fired at non-empty stack actor: " + name) { val stackActor = createNonEmptyStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(!stackInfo.isEmpty) } }
test("Peek is fired at non-empty stack actor: " + name) { val stackActor = createNonEmptyStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePeek <- stackActor ? Size afterPeek <- stackActor ? Peek } yield (beforePeek, afterPeek) futurePair map { case (beforePeek, afterPeek) => assert(afterPeek.top === Some(lastItemAdded)) assert(afterPeek.size === beforePeek.size) } }
test("Pop is fired at non-empty stack actor: " + name) { val stackActor = createNonEmptyStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePop <- stackActor ? Size afterPop <- stackActor ? Pop } yield (beforePop, afterPop) futurePair map { case (beforePop, afterPop) => assert(afterPop.top === Some(lastItemAdded)) assert(afterPop.size === beforePop.size - 1) } } }
def nonFullStackActor(createNonFullStackActor: => StackActor[Int], name: String): Unit = {
test("non-full stack actor is not full: " + name) { val stackActor = createNonFullStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(!stackInfo.isFull) } }
test("Push is fired at non-full stack actor: " + name) { val stackActor = createNonFullStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePush <- stackActor ? Size afterPush <- { stackActor ! Push(7); stackActor ? Peek } } yield (beforePush, afterPush) futurePair map { case (beforePush, afterPush) => assert(afterPush.top === Some(7)) assert(afterPush.size === beforePush.size + 1) } } } }Given these behavior functions, you could invoke them directly, but
AsyncFunSuite
offers a DSL for the purpose, which looks like this:testsFor(nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName)) testsFor(nonFullStackActor(almostEmptyStackActor, almostEmptyStackActorName))
Here's an example:
class StackSuite extends AsyncFunSuite with AsyncFunSuiteStackBehaviors {
val Max = 10 val LastValuePushed = Max - 1
// Stack fixture creation methods val emptyStackActorName = "empty stack actor" def emptyStackActor = new StackActor[Int](Max, emptyStackActorName )
val fullStackActorName = "full stack actor" def fullStackActor = { val stackActor = new StackActor[Int](Max, fullStackActorName ) for (i <- 0 until Max) stackActor ! Push(i) stackActor }
val almostEmptyStackActorName = "almost empty stack actor" def almostEmptyStackActor = { val stackActor = new StackActor[Int](Max, almostEmptyStackActorName ) stackActor ! Push(LastValuePushed) stackActor }
val almostFullStackActorName = "almost full stack actor" def almostFullStackActor = { val stackActor = new StackActor[Int](Max, almostFullStackActorName) for (i <- 1 to LastValuePushed) stackActor ! Push(i) stackActor }
test("an empty stack actor is empty") { val stackActor = emptyStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(stackInfo.isEmpty) } }
test("Peek is fired at an empty stack actor") { recoverToSucceededIf[IllegalStateException] { emptyStackActor ? Peek } }
test("Pop is fired at an empty stack actor") { recoverToSucceededIf[IllegalStateException] { emptyStackActor ? Pop } }
testsFor(nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName)) testsFor(nonFullStackActor(almostEmptyStackActor, almostEmptyStackActorName))
testsFor(nonEmptyStackActor(almostFullStackActor, LastValuePushed, almostFullStackActorName)) testsFor(nonFullStackActor(almostFullStackActor, almostFullStackActorName))
test("a full stack actor is full") { val stackActor = fullStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(stackInfo.isFull) } }
testsFor(nonEmptyStackActor(fullStackActor, LastValuePushed, fullStackActorName))
test("Push is fired at a full stack actor") { val stackActor = fullStackActor assertThrows[IllegalStateException] { stackActor ! Push(10) } } }If you load these classes into the Scala interpreter (with scalatest's JAR file on the class path), and execute it, you'll see:
scala> org.scalatest.run(new StackSuite) StackSuite: StackSuite: - an empty stack actor is empty - Peek is fired at an empty stack actor - Pop is fired at an empty stack actor - Size is fired at non-empty stack actor: almost empty stack actor - Peek is fired at non-empty stack actor: almost empty stack actor - Pop is fired at non-empty stack actor: almost empty stack actor - non-full stack actor is not full: almost empty stack actor - Push is fired at non-full stack actor: almost empty stack actor - Size is fired at non-empty stack actor: almost full stack actor - Peek is fired at non-empty stack actor: almost full stack actor - Pop is fired at non-empty stack actor: almost full stack actor - non-full stack actor is not full: almost full stack actor - Push is fired at non-full stack actor: almost full stack actor - a full stack actor is full - Size is fired at non-empty stack actor: full stack actor - Peek is fired at non-empty stack actor: full stack actor - Pop is fired at non-empty stack actor: full stack actor - Push is fired at a full stack actor
One thing to keep in mind when using shared tests is that in ScalaTest, each test in a suite must have a unique name. If you register the same tests repeatedly in the same suite, one problem you may encounter is an exception at runtime complaining that multiple tests are being registered with the same test name. In a
AsyncFunSuite
there is no nesting construct analogous toAsyncFunSpec
'sdescribe
clause. Therefore, you need to do a bit of extra work to ensure that the test names are unique. If a duplicate test name problem shows up in anAsyncFunSuite
, you'll need to pass in a prefix or suffix string to add to each test name. You can calltoString
on the shared fixture object, or pass this string the same way you pass any other data needed by the shared tests. This is the approach taken by the previousAsyncFunSuiteStackBehaviors
example.Given this
AsyncFunSuiteStackBehaviors
trait, calling it with thestackWithOneItem
fixture, like this:testsFor(nonEmptyStackActor(almostFullStackActor, LastValuePushed, almostFullStackActorName))
yields test names:
Size is fired at non-empty stack actor: almost empty stack actor
Peek is fired at non-empty stack actor: almost empty stack actor
Pop is fired at non-empty stack actor: almost empty stack actor
Whereas calling it with the
stackWithOneItemLessThanCapacity
fixture, like this:testsFor(nonEmptyStack(stackWithOneItemLessThanCapacity, lastValuePushed))
yields different test names:
Size is fired at non-empty stack actor: almost full stack actor
Peek is fired at non-empty stack actor: almost full stack actor
Pop is fired at non-empty stack actor: almost full stack actor
- trait AsyncFunSuiteLike extends AsyncTestSuite with AsyncTestRegistration with Informing with Notifying with Alerting with Documenting
Implementation trait for class
AsyncFunSuite
, which represents a suite of tests in which each test is represented as a function value.Implementation trait for class
AsyncFunSuite
, which represents a suite of tests in which each test is represented as a function value.AsyncFunSuite
is a class, not a trait, to minimize compile time given there is a slight compiler overhead to mixing in traits compared to extending classes. If you need to mix the behavior ofAsyncFunSuite
into some other class, you can use this trait instead, because classAsyncFunSuite
does nothing more than extend this trait and add a nicetoString
implementation.See the documentation of the class for a detailed overview of
AsyncFunSuite
. - trait AsyncTestRegistration extends AnyRef
Trait declaring methods that can be used to register by-name test functions that have result type
Future[Assertion]
. - trait AsyncTestSuite extends Suite with RecoverMethods with CompleteLastly
The base trait of ScalaTest's asynchronous testing styles, which defines a
withFixture
lifecycle method that accepts as its parameter a test function that returns aFutureOutcome
.The base trait of ScalaTest's asynchronous testing styles, which defines a
withFixture
lifecycle method that accepts as its parameter a test function that returns aFutureOutcome
.The
withFixture
method add by this trait has the following signature and implementation:def withFixture(test: NoArgAsyncTest): FutureOutcome = { test() }
This trait enables testing of asynchronous code without blocking. Instead of returning
Outcome
likeTestSuite
'swithFixture
, this trait'swithFixture
method returns aFutureOutcome
. Similarly, theapply
method of test function interface,NoArgAsyncTest
, returnsFutureOutcome
:// In trait NoArgAsyncTest: def apply(): FutureOutcome
The
withFixture
method supports async testing, because when the test function returns, the test body has not necessarily finished execution.The recommended way to ensure cleanup is performed after a test body finishes execution is to use a
complete
-lastly
clause, syntax that is defined in traitCompleteLastly
, which this trait extends. Usingcleanup
-lastly
will ensure that cleanup will occur whetherFutureOutcome
-producing code completes abruptly by throwing an exception, or returns normally yielding aFutureOutcome
. In the latter case,complete
-lastly
will register the cleanup code to execute asynchronously when theFutureOutcome
completes.The
withFixture
method is designed to be stacked, and to enable this, you should always call thesuper
implementation ofwithFixture
, 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)
”. Thus, the recommended structure of awithFixture
implementation that performs cleanup looks 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 }
The test function and
withFixture
method returns aFutureOutcome
, a ScalaTest class that wraps a ScalaFuture[Outcome]
and offers methods more specific to asynchronous test outcomes. In a ScalaFuture
, any exception results in ascala.util.Failure
. In aFutureOutcome
, a thrownTestPendingException
always results in aPending
, a thrownTestCanceledException
always results in aCanceled
, and any other exception, so long as it isn't suite-aborting, results in aFailed
. This is true of the asynchronous test code itself that's represented by theFutureOutcome
and any transformation or callback registered on theFutureOutcome
inwithFixture
.If you want to perform an action only for certain outcomes, you'll need to register code performing that action on the
FutureOutcome
using one ofFutureOutcome
's callback registration methods:onSucceededThen
- executed if theOutcome
is aSucceeded
.onFailedThen
- executed if theOutcome
is aFailed
.onCanceledThen
- executed if theOutcome
is aCanceled
.onPendingThen
- executed if theOutcome
is aPending
.onOutcomeThen
- executed on anyOutcome
(i.e., no suite-aborting exception is thrown).onAbortedThen
- executed if a suite-aborting exception is thrown.onCompletedThen
- executed whether the result is anOutcome
or a thrown suite-aborting exception.
For example, if you want to perform an action if a test fails, you'd register the callback using
onFailedThen
, like this:// Your implementation override def withFixture(test: NoArgAsyncTest) = {
// Perform setup here
val futureOutcome = super.withFixture(test) // Invoke the test function
futureOutcome onFailedThen { ex => // perform action that you want to occur // only if a test fails here } }Note that all callback registration methods, such as
onFailedThen
used in the previous example, return a newFutureOutcome
that won't complete until the the originalFutureOutcome
and the callback has completed. If the callback throws an exception, the resultingFutureOutcome
will represent that exception. For example, if aFutureOutcome
results inFailed
, but a callback registered on thatFutureOutcome
withonFailedThen
throwsTestPendingException
, the result of theFutureOutcome
returned byonFailedThen
will bePending
.Lastly, if you want to change the outcome in some way in
withFixture
, you'll need to use thechange
method ofFutureOutcome
, 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 } } - trait AsyncTestSuiteMixin extends SuiteMixin
Trait defining abstract "lifecycle" methods that are implemented in
AsyncTestSuite
and can be overridden in stackable modification traits.Trait defining abstract "lifecycle" methods that are implemented in
AsyncTestSuite
and can be overridden in stackable modification traits.The main use case for this trait is to override
withFixture
in a mixin trait. Here's an example:trait Builder extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val builder = new ThreadSafeStringBuilder
abstract override def withFixture(test: NoArgAsyncTest) = { builder.append("ScalaTest is ") complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { builder.clear() } } } - abstract class AsyncWordSpec extends AsyncWordSpecLike
Enables testing of asynchronous code without blocking, using a style consistent with traditional
WordSpec
tests.Enables testing of asynchronous code without blocking, using a style consistent with traditional
WordSpec
tests.Recommended Usage: AsyncFunSpec
is intended to enable users ofFunSpec
to write non-blocking asynchronous tests that are consistent with their traditionalFunSpec
tests. Note:AsyncFunSpec
is intended for use in special situations where non-blocking asynchronous testing is needed, with classFunSpec
used for general needs.Given a
Future
returned by the code you are testing, you need not block until theFuture
completes before performing assertions against its value. You can instead map those assertions onto theFuture
and return the resultingFuture[Assertion]
to ScalaTest. The test will complete asynchronously, when theFuture[Assertion]
completes.Here's an example
AsyncWordSpec
:package org.scalatest.examples.asyncwordspec
import org.scalatest.AsyncWordSpec import scala.concurrent.Future
class AddSpec extends AsyncWordSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" 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) } } }“
it
” is a method, defined inAsyncWordSpec
, which will be invoked by the primary constructor ofAddSpec
. You specify the name of the test as a string between the parentheses, and the test code itself between curly braces. The test code is a function passed as a by-name parameter toit
, which registers it for later execution. The result type of the by-name in anAsyncWordSpec
must beFuture[Assertion]
.In an
AsyncWordSpec
you write a one (or more) sentence specification for each bit of behavior you wish to specify and test. Each specification sentence has a "subject," which is sometimes called the system under test (or SUT). The subject is entity being specified and tested and also serves as the subject of the sentences you write for each test. A subject can be followed by one of three verbs,should
,must
, orcan
, and a block. Here are some examples:"A Stack" should { // ... } "An Account" must { // ... } "A ShippingManifest" can { // ... }
You can describe a subject in varying situations by using a
when
clause. Awhen
clause follows the subject and precedes a block. In the block after thewhen
, you place strings that describe a situation or a state the subject may be in using a string, each followed by a verb. Here's an example:"A Stack" when { "empty" should { // ... } "non-empty" should { // ... } "full" should { // ... } }
When you are ready to finish a sentence, you write a string followed by
in
and a block that contains the code of the test. In short, you structure anAsyncWordSpec
exactly like aWordSpec
, but with tests having result typeAssertion
orFuture[Assertion]
. For more examples of structure, see the documentation forWordSpec
.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, isFuture[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 toFuture[Assertion]
and registered. The implicit conversion is fromAssertion
toFuture[Assertion]
, so you must end synchronous tests in some ScalaTest assertion or matcher expression. If a test would not otherwise end in typeAssertion
, you can placesucceed
at the end of the test.succeed
, a field in traitAssertions
, returns theSucceeded
singleton:scala> succeed res2: org.scalatest.Assertion = Succeeded
Thus placing
succeed
at the end of a test body will satisfy the type checker:"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 }
An
AsyncWordSpec
's lifecycle has two phases: the registration phase and the ready phase. It starts in registration phase and enters ready phase the first timerun
is called on it. It then remains in ready phase for the remainder of its lifetime.Tests can only be registered with the
it
method while theAsyncWordSpec
is in its registration phase. Any attempt to register a test after theAsyncWordSpec
has entered its ready phase, i.e., afterrun
has been invoked on theAsyncWordSpec
, will be met with a thrownTestRegistrationClosedException
. The recommended style of usingAsyncWordSpec
is to register tests during object construction as is done in all the examples shown here. If you keep to the recommended style, you should never see aTestRegistrationClosedException
.Asynchronous execution model
AsyncWordSpec
extendsAsyncTestSuite
, which provides an implicitscala.concurrent.ExecutionContext
namedexecutionContext
. This execution context is used byAsyncWordSpec
to transform theFuture[Assertion]
s returned by each test into theFutureOutcome
returned by thetest
function passed towithFixture
. ThisExecutionContext
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 theFutureOutcome
returned from thetest
function. Other tasks that will be queued are any transformations of, or callbacks registered on,Future
s that occur in your test body, including any assertions you map ontoFuture
s. 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 ofFuture
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]
. ThisFuture[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 theFuture[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
WordSpec
withScalaFutures
instead. Alternatively, you could override theexecutionContext
and use a traditionalExecutionContext
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 therunNow
execution context on Scala.js instead of the defaultqueue
, you would write:// on Scala.js implicit override def executionContext = org.scalatest.concurrent.TestExecutionContext.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 anAsyncWordSpec
will be executed one after another, i.e., serially. This is true whether those tests returnAssertion
orFuture[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
AsyncWordSpec
to be executed in parallel, you must mix inParallelTestExecution
and enable parallel execution of tests in your build. You enable parallel execution inRunner
with the-P
command line flag. In the ScalaTest Maven Plugin, setparallel
totrue
. Insbt
, 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 theDistributor
, and allowed to complete in parallel, using threads from theexecutionContext
. If you are using ScalaTest's serial execution context, the JVM default, asynchronous tests will run in parallel very much like traditional (such asWordSpec
) tests run in parallel: 1) BecauseParallelTestExecution
extendsOneInstancePerTest
, 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 ofFuture
s 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 invokedrun
, 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 theexecutionContext
. 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 inParallelTestExecution
is executed: the tests will run sequentially. If you use an execution context backed by a thread-pool, such asglobal
, 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
andrecoverToExceptionIf
methods of traitRecoverMethods
. Because this trait is mixed into supertraitAsyncTestSuite
, both of these methods are available by default in anAsyncWordSpec
.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 toassertThrows
, except in the context of a future. It transforms aFuture
of any type into aFuture[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 therecoverToSucceededIf
in its behavior when the assertion succeeds:recoverToSucceededIf
yields aFuture[Assertion]
, whereasrecoverToExceptionIf
yields aFuture[T]
, whereT
is the expected exception type.recoverToExceptionIf[IllegalStateException] { // Result type: Future[IllegalStateException] emptyStackActor ? Peek }
In other words,
recoverToExpectionIf
is tointercept
asrecovertToSucceededIf
is toassertThrows
. 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 showingrecoverToExceptionIf
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]"))
Ignored tests
To support the common use case of temporarily disabling a test, with the good intention of resurrecting the test at a later time,
AsyncWordSpec
adds a methodignore
to strings that can be used instead ofin
to register a test. For example, to temporarily disable the test with the name"A Stack should pop values in last-in-first-out order"
, just change “in
” into “ignore
,” like this:package org.scalatest.examples.asyncwordspec.ignore
import org.scalatest.AsyncWordSpec import scala.concurrent.Future
class AddSpec extends AsyncWordSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" should { "eventually compute a sum of passed Ints" ignore { 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) } } }If you run this version of
AddSpec
with:scala> org.scalatest.run(new AddSpec)
It will run only the second test and report that the first test was ignored:
AddSpec: addSoon - should eventually compute a sum of passed Ints !!! IGNORED !!! addNow - should immediately compute a sum of passed Ints
If you wish to temporarily ignore an entire suite of tests, you can (on the JVM, not Scala.js) annotate the test class with
@Ignore
, like this:package org.scalatest.examples.asyncwordspec.ignoreall
import org.scalatest.AsyncWordSpec import scala.concurrent.Future import org.scalatest.Ignore
@Ignore class AddSpec extends AsyncWordSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" 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) } } }When you mark a test class with a tag annotation, ScalaTest will mark each test defined in that class with that tag. Thus, marking the
AddSpec
in the above example with the@Ignore
tag annotation means that both tests in the class will be ignored. If you run the aboveAddSpec
in the Scala interpreter, you'll see:AddSpec: addSoon - should eventually compute a sum of passed Ints !!! IGNORED !!! addNow - should immediately compute a sum of passed Ints !!! IGNORED !!!
Note that marking a test class as ignored won't prevent it from being discovered by ScalaTest. Ignored classes will be discovered and run, and all their tests will be reported as ignored. This is intended to keep the ignored class visible, to encourage the developers to eventually fix and “un-ignore” it. If you want to prevent a class from being discovered at all (on the JVM, not Scala.js), use the
DoNotDiscover
annotation instead.If you want to ignore all tests of a suite on Scala.js, where annotations can't be inspected at runtime, you'll need to change
it
toignore
at each test site. To make a suite non-discoverable on Scala.js, ensure it does not declare a public no-arg constructor. You can either declare a public constructor that takes one or more arguments, or make the no-arg constructor non-public. Because this technique will also make the suite non-discoverable on the JVM, it is a good approach for suites you want to run (but not be discoverable) on both Scala.js and the JVM.Informers
One of the parameters to
AsyncWordSpec
'srun
method is aReporter
, which will collect and report information about the running suite of tests. Information about suites and tests that were run, whether tests succeeded or failed, and tests that were ignored will be passed to theReporter
as the suite runs. Most often the reporting done by default byAsyncWordSpec
's methods will be sufficient, but occasionally you may wish to provide custom information to theReporter
from a test. For this purpose, anInformer
that will forward information to the currentReporter
is provided via theinfo
parameterless method. You can pass the extra information to theInformer
via itsapply
method. TheInformer
will then pass the information to theReporter
via anInfoProvided
event.One use case for the
Informer
is to pass more information about a specification to the reporter. For example, theGivenWhenThen
trait provides methods that use the implicitinfo
provided byAsyncWordSpec
to pass such information to the reporter. Here's an example:package org.scalatest.examples.asyncwordspec.info
import collection.mutable import org.scalatest._
class SetSpec extends AsyncWordSpec with GivenWhenThen {
"A mutable Set" should { "allow an element to be added" in { Given("an empty mutable Set") val set = mutable.Set.empty[String]
When("an element is added") set += "clarity"
Then("the Set should have size 1") assert(set.size === 1)
And("the Set should contain the added element") assert(set.contains("clarity"))
info("That's all folks!") succeed } } }If you run this
AsyncWordSpec
from the interpreter, you will see the following output:scala> org.scalatest.run(new SetSpec) A mutable Set - should allow an element to be added + Given an empty mutable Set + When an element is added + Then the Set should have size 1 + And the Set should contain the added element + That's all folks!
Documenters
AsyncWordSpec
also provides amarkup
method that returns aDocumenter
, which allows you to send to theReporter
text formatted in Markdown syntax. You can pass the extra information to theDocumenter
via itsapply
method. TheDocumenter
will then pass the information to theReporter
via anMarkupProvided
event.Here's an example
AsyncWordSpec
that usesmarkup
:package org.scalatest.examples.asyncwordspec.markup
import collection.mutable import org.scalatest._
class SetSpec extends AsyncWordSpec with GivenWhenThen {
markup { """ Mutable Set ———-- A set is a collection that contains no duplicate elements. To implement a concrete mutable set, you need to provide implementations of the following methods: def contains(elem: A): Boolean def iterator: Iterator[A] def += (elem: A): this.type def -= (elem: A): this.type If you wish that methods like `take`, `drop`, `filter` return the same kind of set, you should also override: def empty: This It is also good idea to override methods `foreach` and `size` for efficiency. """ }
"A mutable Set" should { "allow an element to be added" in { Given("an empty mutable Set") val set = mutable.Set.empty[String]
When("an element is added") set += "clarity"
Then("the Set should have size 1") assert(set.size === 1)
And("the Set should contain the added element") assert(set.contains("clarity"))
markup("This test finished with a **bold** statement!") succeed } } }Although all of ScalaTest's built-in reporters will display the markup text in some form, the HTML reporter will format the markup information into HTML. Thus, the main purpose of
markup
is to add nicely formatted text to HTML reports. Here's what the aboveSetSpec
would look like in the HTML reporter:Notifiers and alerters
ScalaTest records text passed to
info
andmarkup
during tests, and sends the recorded text in therecordedEvents
field of test completion events likeTestSucceeded
andTestFailed
. This allows string reporters (like the standard out reporter) to showinfo
andmarkup
text after the test name in a color determined by the outcome of the test. For example, if the test fails, string reporters will show theinfo
andmarkup
text in red. If a test succeeds, string reporters will show theinfo
andmarkup
text in green. While this approach helps the readability of reports, it means that you can't useinfo
to get status updates from long running tests.To get immediate (i.e., non-recorded) notifications from tests, you can use
note
(aNotifier
) andalert
(anAlerter
). Here's an example showing the differences:package org.scalatest.examples.asyncwordspec.note
import collection.mutable import org.scalatest._
class SetSpec extends AsyncWordSpec {
"A mutable Set" should { "allow an element to be added" in {
info("info is recorded") markup("markup is *also* recorded") note("notes are sent immediately") alert("alerts are also sent immediately")
val set = mutable.Set.empty[String] set += "clarity" assert(set.size === 1) assert(set.contains("clarity")) } } }Because
note
andalert
information is sent immediately, it will appear before the test name in string reporters, and its color will be unrelated to the ultimate outcome of the test:note
text will always appear in green,alert
text will always appear in yellow. Here's an example:scala> org.scalatest.run(new SetSpec) SetSpec: A mutable Set + notes are sent immediately + alerts are also sent immediately - should allow an element to be added + info is recorded + markup is *also* recorded
Another example is slowpoke notifications. If you find a test is taking a long time to complete, but you're not sure which test, you can enable slowpoke notifications. ScalaTest will use an
Alerter
to fire an event whenever a test has been running longer than a specified amount of time.In summary, use
info
andmarkup
for text that should form part of the specification output. Usenote
andalert
to send status notifications. (Because the HTML reporter is intended to produce a readable, printable specification,info
andmarkup
text will appear in the HTML report, butnote
andalert
text will not.)Pending tests
A pending test is one that has been given a name but is not yet implemented. The purpose of pending tests is to facilitate a style of testing in which documentation of behavior is sketched out before tests are written to verify that behavior (and often, before the behavior of the system being tested is itself implemented). Such sketches form a kind of specification of what tests and functionality to implement later.
To support this style of testing, a test can be given a name that specifies one bit of behavior required by the system being tested. At the end of the test, it can call method
pending
, which will cause it to complete abruptly withTestPendingException
.Because tests in ScalaTest can be designated as pending with
TestPendingException
, both the test name and any information sent to the reporter when running the test can appear in the report of a test run. (In other words, the code of a pending test is executed just like any other test.) However, because the test completes abruptly withTestPendingException
, the test will be reported as pending, to indicate the actual test, and possibly the functionality, has not yet been implemented. Here's an example:package org.scalatest.examples.asyncwordspec.pending
import org.scalatest.AsyncWordSpec import scala.concurrent.Future
class AddSpec extends AsyncWordSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" should { "eventually compute a sum of passed Ints" in (pending) }
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) } } }(Note: "
(pending)
" is the body of the test. Thus the test contains just one statement, an invocation of thepending
method, which throwsTestPendingException
.) If you run this version ofAddSpec
with:scala> org.scalatest.run(new AddSpec)
It will run both tests, but report that first test is pending. You'll see:
AddSpec: addSoon - should eventually compute a sum of passed Ints (pending) addNow - should immediately compute a sum of passed Ints
One difference between an ignored test and a pending one is that an ignored test is intended to be used during significant refactorings of the code under test, when tests break and you don't want to spend the time to fix all of them immediately. You can mark some of those broken tests as ignored temporarily, so that you can focus the red bar on just failing tests you actually want to fix immediately. Later you can go back and fix the ignored tests. In other words, by ignoring some failing tests temporarily, you can more easily notice failed tests that you actually want to fix. By contrast, a pending test is intended to be used before a test and/or the code under test is written. Pending indicates you've decided to write a test for a bit of behavior, but either you haven't written the test yet, or have only written part of it, or perhaps you've written the test but don't want to implement the behavior it tests until after you've implemented a different bit of behavior you realized you need first. Thus ignored tests are designed to facilitate refactoring of existing code whereas pending tests are designed to facilitate the creation of new code.
One other difference between ignored and pending tests is that ignored tests are implemented as a test tag that is excluded by default. Thus an ignored test is never executed. By contrast, a pending test is implemented as a test that throws
TestPendingException
(which is what calling thepending
method does). Thus the body of pending tests are executed up until they throwTestPendingException
.Tagging tests
An
AsyncFunSpec
's tests may be classified into groups by tagging them with string names. As with any suite, when executing anAsyncFunSpec
, groups of tests can optionally be included and/or excluded. To tag anAsyncFunSpec
's tests, you pass objects that extend classorg.scalatest.Tag
to methods that register tests. ClassTag
takes one parameter, a string name. If you have created tag annotation interfaces as described in theTag
documentation, then you will probably want to use tag names on your test functions that match. To do so, simply pass the fully qualified names of the tag interfaces to theTag
constructor. For example, if you've defined a tag annotation interface with fully qualified name,com.mycompany.tags.DbTest
, then you could create a matching tag forAsyncFunSpec
s like this:package org.scalatest.examples.asyncwordspec.tagging
import org.scalatest.Tag
object DbTest extends Tag("com.mycompany.tags.DbTest")Given these definitions, you could place
AsyncFunSpec
tests into groups with tags like this:import org.scalatest.AsyncWordSpec import org.scalatest.tagobjects.Slow import scala.concurrent.Future
class AddSpec extends AsyncWordSpec {
def addSoon(addends: Int*): Future[Int] = Future { addends.sum }
"addSoon" should { "eventually compute a sum of passed Ints" taggedAs(Slow) 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" taggedAs(Slow, DbTest) in {
val sum: Int = addNow(1, 2) // You can also write synchronous tests. The body // must have result type Assertion: assert(sum == 3) } } }This code marks both tests with the
org.scalatest.tags.Slow
tag, and the second test with thecom.mycompany.tags.DbTest
tag.The
run
method takes aFilter
, whose constructor takes an optionalSet[String]
calledtagsToInclude
and aSet[String]
calledtagsToExclude
. IftagsToInclude
isNone
, all tests will be run except those those belonging to tags listed in thetagsToExclude
Set
. IftagsToInclude
is defined, only tests belonging to tags mentioned in thetagsToInclude
set, and not mentioned intagsToExclude
, will be run.It is recommended, though not required, that you create a corresponding tag annotation when you create a
Tag
object. A tag annotation (on the JVM, not Scala.js) allows you to tag all the tests of anAsyncFunSpec
in one stroke by annotating the class. For more information and examples, see the documentation for classTag
. On Scala.js, to tag all tests of a suite, you'll need to tag each test individually at the test site.Shared fixtures
A test fixture is composed of the objects and other artifacts (files, sockets, database connections, etc.) tests use to do their work. When multiple tests need to work with the same fixtures, it is important to try and avoid duplicating the fixture code across those tests. The more code duplication you have in your tests, the greater drag the tests will have on refactoring the actual production code.
ScalaTest recommends three techniques to eliminate such code duplication in async styles:
- Refactor using Scala
- Override
withFixture
- Mix in a before-and-after trait
Each technique is geared towards helping you reduce code duplication without introducing instance
var
s, shared mutable objects, or other dependencies between tests. Eliminating shared mutable state across tests will make your test code easier to reason about and eliminate the need to synchronize access to shared mutable state on the JVM.The following sections describe these techniques, including explaining the recommended usage for each. But first, here's a table summarizing the options:
Refactor using Scala when different tests need different fixtures. get-fixture methods The extract method refactor helps you create a fresh instances of mutable fixture objects in each test that needs them, but doesn't help you clean them up when you're done. loan-fixture methods Factor out dupicate code with the loan pattern when different tests need different fixtures that must be cleaned up afterwards. Override withFixture
when most or all tests need the same fixture.withFixture(NoArgAsyncTest)
The recommended default approach when most or all tests need the same fixture treatment. This general technique allows you, for example, to perform side effects at the beginning and end of all or most tests, transform the outcome of tests, retry tests, make decisions based on test names, tags, or other test data. Use this technique unless: - Different tests need different fixtures (refactor using Scala instead)
- An exception in fixture code should abort the suite, not fail the test (use a before-and-after trait instead)
- You have objects to pass into tests (override
withFixture(OneArgAsyncTest)
instead)
withFixture(OneArgAsyncTest)
Use when you want to pass the same fixture object or objects as a parameter into all or most tests. Mix in a before-and-after trait when you want an aborted suite, not a failed test, if the fixture code fails. BeforeAndAfter
Use this boilerplate-buster when you need to perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. BeforeAndAfterEach
Use when you want to stack traits that perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. Calling get-fixture methods
If you need to create the same mutable fixture objects in multiple tests, and don't need to clean them up after using them, the simplest approach is to write one or more get-fixture methods. A get-fixture method returns a new instance of a needed fixture object (or a holder object containing multiple fixture objects) each time it is called. You can call a get-fixture method at the beginning of each test that needs the fixture, storing the returned object or objects in local variables. Here's an example:
package org.scalatest.examples.asyncwordspec.getfixture
import org.scalatest.AsyncWordSpec import scala.concurrent.Future
class ExampleSpec extends AsyncWordSpec {
def fixture: Future[String] = Future { "ScalaTest is " }
"Testing" should { "be easy" in { val future = fixture val result = future map { s => s + "easy!" } result map { s => assert(s == "ScalaTest is easy!") } }
"be fun" in { val future = fixture val result = future map { s => s + "fun!" } result map { s => assert(s == "ScalaTest is fun!") } } } }If you need to configure fixture objects differently in different tests, you can pass configuration into the get-fixture method. For example, you could pass in an initial value for a fixture object as a parameter to the get-fixture method.
Overriding
withFixture(NoArgAsyncTest)
Although the get-fixture method approach takes care of setting up a fixture at the beginning of each test, it doesn't address the problem of cleaning up a fixture at the end of the test. If you just need to perform a side-effect at the beginning or end of a test, and don't need to actually pass any fixture objects into the test, you can override
withFixture(NoArgAsyncTest)
, a method defined in traitAsyncTestSuite
, a supertrait ofAsyncFunSpec
.Trait
AsyncFunSpec
'srunTest
method passes a no-arg async test function towithFixture(NoArgAsyncTest)
. It iswithFixture
's responsibility to invoke that test function. The default implementation ofwithFixture
simply invokes the function and returns the result, like this:// 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 thecomplete
-lastly
syntax, defined in supertraitCompleteLastly
. Thecomplete
-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 thesuper
implementation ofwithFixture
, 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 ofFuture
's registration methods:onComplete
,onSuccess
, oronFailure
. Note that if a test fails, that will be treated as ascala.util.Success(org.scalatest.Failed)
. So if you want to perform an action if a test fails, for example, you'd register the callback usingonSuccess
.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.asyncwordspec.noargasynctest
import java.io.File import org.scalatest._ import scala.concurrent.Future
class ExampleSpec extends AsyncWordSpec {
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) } }
"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
andworld.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 towithFixture
, in addition to anapply
method that executes the test, also includes the test name and the config map passed torunTest
. Thus you can also use the test name and configuration objects in yourwithFixture
implementation.Lastly, if you want to transform the outcome in some way in
withFixture
, you'll need to use either themap
ortransform
methods ofFuture
, 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
'sapply
method will return ascala.util.Failure
only if the test completes abruptly with a "test-fatal" exception (such asOutOfMemoryError
) that should cause the suite to abort rather than the test to fail. Thus usually you would usemap
to transform future outcomes, nottransform
, so that such test-fatal exceptions pass through unchanged. The suite will abort asynchronously with any exception returned fromNoArgAsyncTest
's apply method in ascala.util.Failure
.Calling loan-fixture methods
If you need to both pass a fixture object into a test and perform cleanup at the end of the test, you'll need to use the loan pattern. If different tests need different fixtures that require cleanup, you can implement the loan pattern directly by writing loan-fixture methods. A loan-fixture method takes a function whose body forms part or all of a test's code. It creates a fixture, passes it to the test code by invoking the function, then cleans up the fixture after the function returns.
The following example shows three tests that use two fixtures, a database and a file. Both require cleanup after, so each is provided via a loan-fixture method. (In this example, the database is simulated with a
StringBuffer
.)package org.scalatest.examples.asyncwordspec.loanfixture
import java.util.concurrent.ConcurrentHashMap
import scala.concurrent.Future import scala.concurrent.ExecutionContext
object DbServer { // Simulating a database server type Db = StringBuffer private final val databases = new ConcurrentHashMap[String, Db] def createDb(name: String): Db = { val db = new StringBuffer // java.lang.StringBuffer is thread-safe databases.put(name, db) db } def removeDb(name: String): Unit = { databases.remove(name) } }
// 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 } } }
import org.scalatest._ import DbServer._ import java.util.UUID.randomUUID
class ExampleSpec extends AsyncWordSpec {
def withDatabase(testCode: Future[Db] => Future[Assertion]) = { val dbName = randomUUID.toString // generate a unique db name val futureDb = Future { createDb(dbName) } // create the fixture complete { val futurePopulatedDb = futureDb map { db => db.append("ScalaTest is ") // perform setup } testCode(futurePopulatedDb) // "loan" the fixture to the test code } lastly { removeDb(dbName) // ensure the fixture will be cleaned up } }
def withActor(testCode: StringActor => Future[Assertion]) = { val actor = new StringActor complete { actor ! Append("ScalaTest is ") // set up the fixture testCode(actor) // "loan" the fixture to the test code } lastly { actor ! Clear // ensure the fixture will be cleaned up } }
"Testing" should { // This test needs the actor fixture "be productive" in { withActor { actor => actor ! Append("productive!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is productive!") } } } }
"Test code" should { // This test needs the database fixture "be readable" in { withDatabase { futureDb => futureDb map { db => db.append("readable!") assert(db.toString == "ScalaTest is readable!") } } }
// This test needs both the actor and the database "be clear and concise" in { withDatabase { futureDb => withActor { actor => // loan-fixture methods compose actor ! Append("concise!") val futureString = actor ? GetValue val futurePair: Future[(Db, String)] = futureDb zip futureString futurePair map { case (db, s) => db.append("clear!") assert(db.toString == "ScalaTest is clear!") assert(s == "ScalaTest is concise!") } } } } } }As demonstrated by the last test, loan-fixture methods compose. Not only do loan-fixture methods allow you to give each test the fixture it needs, they allow you to give a test multiple fixtures and clean everything up afterwards.
Also demonstrated in this example is the technique of giving each test its own "fixture sandbox" to play in. When your fixtures involve external side-effects, like creating databases, it is a good idea to give each database a unique name as is done in this example. This keeps tests completely isolated, allowing you to run them in parallel if desired.
Overriding
withFixture(OneArgTest)
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 overridingwithFixture(OneArgAsyncTest)
. Each test in afixture.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 specifyingFixtureParam
, and implement awithFixture
method that takes aOneArgAsyncTest
. ThiswithFixture
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 letwithFixture(NoArgAsyncTest)
invoke the test function instead of invoking the test function directly. To do so, you'll need to convert theOneArgAsyncTest
to aNoArgAsyncTest
. You can do that by passing the fixture object to thetoNoArgAsyncTest
method ofOneArgAsyncTest
. In other words, instead of writing “test(theFixture)
”, you'd delegate responsibility for invoking the test function to thewithFixture(NoArgAsyncTest)
method of the same instance by writing:withFixture(test.toNoArgAsyncTest(theFixture))
Here's a complete example:
package org.scalatest.examples.asyncwordspec.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.AsyncWordSpec {
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!") } }
"be fun" in { actor => actor ! Append("fun!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is fun!") } } } }In this example, the tests required one fixture object, a
StringActor
. If your tests need multiple fixture objects, you can simply define theFixtureParam
type to be a tuple containing the objects or, alternatively, a case class containing the objects. For more information on thewithFixture(OneArgAsyncTest)
technique, see the documentation forfixture.AsyncFunSpec
.Mixing in
BeforeAndAfter
In all the shared fixture examples shown so far, the activities of creating, setting up, and cleaning up the fixture objects have been performed during the test. This means that if an exception occurs during any of these activities, it will be reported as a test failure. Sometimes, however, you may want setup to happen before the test starts, and cleanup after the test has completed, so that if an exception occurs during setup or cleanup, the entire suite aborts and no more tests are attempted. The simplest way to accomplish this in ScalaTest is to mix in trait
BeforeAndAfter
. With this trait you can denote a bit of code to run before each test withbefore
and/or after each test each test withafter
, like this:package org.scalatest.examples.asyncwordspec.beforeandafter
import org.scalatest.AsyncWordSpec import org.scalatest.BeforeAndAfter 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 AsyncWordSpec with BeforeAndAfter {
final val actor = new StringActor
before { actor ! Append("ScalaTest is ") // set up the fixture }
after { actor ! Clear // clean up the fixture }
"Testing" should { "be easy" in { actor ! Append("easy!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is easy!") } }
"be fun" in { actor ! Append("fun!") val futureString = actor ? GetValue futureString map { s => assert(s == "ScalaTest is fun!") } } } }Note that the only way
before
andafter
code can communicate with test code is via some side-effecting mechanism, commonly by reassigning instancevar
s or by changing the state of mutable objects held from instanceval
s (as in this example). If using instancevar
s or mutable objects held from instanceval
s you wouldn't be able to run tests in parallel in the same instance of the test class (on the JVM, not Scala.js) unless you synchronized access to the shared, mutable state.Note that on the JVM, if you override ScalaTest's default serial execution context, you will likely need to worry about synchronizing access to shared mutable fixture state, because the execution context may assign different threads to process different
Future
transformations. Although access to mutable state along the same linear chain ofFuture
transformations need not be synchronized, it can be difficult to spot cases where these constraints are violated. The best approach is to use only immutable objects when transformingFuture
s. When that's not practical, involve only thread-safe mutable objects, as is done in the above example. On Scala.js, by contrast, you need not worry about thread synchronization, because in effect only one thread exists.Although
BeforeAndAfter
provides a minimal-boilerplate way to execute code before and after tests, it isn't designed to enable stackable traits, because the order of execution would be non-obvious. If you want to factor out before and after code that is common to multiple test suites, you should use traitBeforeAndAfterEach
instead, as shown later in the next section, composing fixtures by stacking traits.Composing fixtures by stacking traits
In larger projects, teams often end up with several different fixtures that test classes need in different combinations, and possibly initialized (and cleaned up) in different orders. A good way to accomplish this in ScalaTest is to factor the individual fixtures into traits that can be composed using the stackable trait pattern. This can be done, for example, by placing
withFixture
methods in several traits, each of which callsuper.withFixture
. Here's an example in which theStringBuilderActor
andStringBufferActor
fixtures used in the previous examples have been factored out into two stackable fixture traits namedBuilder
andBuffer
:package org.scalatest.examples.asyncwordspec.composingwithasyncfixture
import org.scalatest._ import org.scalatest.SuiteMixin import collection.mutable.ListBuffer 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 StringBuilderActor { // 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 StringBufferActor { private final val buf = ListBuffer.empty[String] def !(op: StringOp): Unit = synchronized { op match { case Append(value) => buf += value case Clear => buf.clear() } } def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[List[String]] = Future { synchronized { buf.toList } } }
trait Builder extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val builderActor = new StringBuilderActor
abstract override def withFixture(test: NoArgAsyncTest) = { builderActor ! Append("ScalaTest is ") complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { builderActor ! Clear } } }
trait Buffer extends AsyncTestSuiteMixin { this: AsyncTestSuite =>
final val bufferActor = new StringBufferActor
abstract override def withFixture(test: NoArgAsyncTest) = { complete { super.withFixture(test) // To be stackable, must call super.withFixture } lastly { bufferActor ! Clear } } }
class ExampleSpec extends AsyncWordSpec with Builder with Buffer {
"Testing" should { "be easy" in { builderActor ! Append("easy!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is easy!") assert(lst.isEmpty) bufferActor ! Append("sweet") succeed } }
"be fun" in { builderActor ! Append("fun!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is fun!") assert(lst.isEmpty) bufferActor ! Append("awesome") succeed } } } }By mixing in both the
Builder
andBuffer
traits,ExampleSpec
gets both fixtures, which will be initialized before each test and cleaned up after. The order the traits are mixed together determines the order of execution. In this case,Builder
is “super” toBuffer
. If you wantedBuffer
to be “super” toBuilder
, you need only switch the order you mix them together, like this:class Example2Spec extends AsyncWordSpec with Buffer with Builder
If you only need one fixture you mix in only that trait:
class Example3Spec extends AsyncWordSpec with Builder
Another way to create stackable fixture traits is by extending the
BeforeAndAfterEach
and/orBeforeAndAfterAll
traits.BeforeAndAfterEach
has abeforeEach
method that will be run before each test (like JUnit'ssetUp
), and anafterEach
method that will be run after (like JUnit'stearDown
). Similarly,BeforeAndAfterAll
has abeforeAll
method that will be run before all tests, and anafterAll
method that will be run after all tests. Here's what the previously shown example would look like if it were rewritten to use theBeforeAndAfterEach
methods instead ofwithFixture
:package org.scalatest.examples.asyncwordspec.composingbeforeandaftereach
import org.scalatest._ import org.scalatest.BeforeAndAfterEach import collection.mutable.ListBuffer 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 StringBuilderActor { // 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 StringBufferActor { private final val buf = ListBuffer.empty[String] def !(op: StringOp): Unit = synchronized { op match { case Append(value) => buf += value case Clear => buf.clear() } } def ?(get: GetValue.type)(implicit c: ExecutionContext): Future[List[String]] = Future { synchronized { buf.toList } } }
trait Builder extends BeforeAndAfterEach { this: Suite =>
final val builderActor = new StringBuilderActor
override def beforeEach() { builderActor ! Append("ScalaTest is ") super.beforeEach() // To be stackable, must call super.beforeEach }
override def afterEach() { try super.afterEach() // To be stackable, must call super.afterEach finally builderActor ! Clear } }
trait Buffer extends BeforeAndAfterEach { this: Suite =>
final val bufferActor = new StringBufferActor
override def afterEach() { try super.afterEach() // To be stackable, must call super.afterEach finally bufferActor ! Clear } }
class ExampleSpec extends AsyncWordSpec with Builder with Buffer {
"Testing" should {
"be easy" in { builderActor ! Append("easy!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is easy!") assert(lst.isEmpty) bufferActor ! Append("sweet") succeed } }
"be fun" in { builderActor ! Append("fun!") val futureString = builderActor ? GetValue val futureList = bufferActor ? GetValue val futurePair: Future[(String, List[String])] = futureString zip futureList futurePair map { case (str, lst) => assert(str == "ScalaTest is fun!") assert(lst.isEmpty) bufferActor ! Append("awesome") succeed } } } }To get the same ordering as
withFixture
, place yoursuper.beforeEach
call at the end of eachbeforeEach
method, and thesuper.afterEach
call at the beginning of eachafterEach
method, as shown in the previous example. It is a good idea to invokesuper.afterEach
in atry
block and perform cleanup in afinally
clause, as shown in the previous example, because this ensures the cleanup code is performed even ifsuper.afterEach
throws an exception.The difference between stacking traits that extend
BeforeAndAfterEach
versus traits that implementwithFixture
is that setup and cleanup code happens before and after the test inBeforeAndAfterEach
, but at the beginning and end of the test inwithFixture
. Thus if awithFixture
method completes abruptly with an exception, it is considered a failed test. By contrast, if any of thebeforeEach
orafterEach
methods ofBeforeAndAfterEach
complete abruptly, it is considered an aborted suite, which will result in aSuiteAborted
event.Shared tests
Sometimes you may want to run the same test code on different fixture objects. In other words, you may want to write tests that are "shared" by different fixture objects. To accomplish this in an
AsyncFunSpec
, you first place shared tests in behavior functions. These behavior functions will be invoked during the construction phase of anyAsyncFunSpec
that uses them, so that the tests they contain will be registered as tests in thatAsyncFunSpec
. For example, given thisStackActor
class:package org.scalatest.examples.asyncwordspec.sharedtests
import scala.collection.mutable.ListBuffer import scala.concurrent.Future import scala.concurrent.ExecutionContext
// Stack operations case class Push[T](value: T) sealed abstract class StackOp case object Pop extends StackOp case object Peek extends StackOp case object Size extends StackOp
// Stack info case class StackInfo[T](top: Option[T], size: Int, max: Int) { require(size > 0, "size was less than zero") require(max > size, "max was less than size") val isFull: Boolean = size == max val isEmpty: Boolean = size == 0 }
class StackActor[T](Max: Int, name: String) {
private final val buf = new ListBuffer[T]
def !(push: Push[T]): Unit = synchronized { if (buf.size != Max) buf.prepend(push.value) else throw new IllegalStateException("can't push onto a full stack") }
def ?(op: StackOp)(implicit c: ExecutionContext): Future[StackInfo[T]] = synchronized { op match { case Pop => Future { if (buf.size != 0) StackInfo(Some(buf.remove(0)), buf.size, Max) else throw new IllegalStateException("can't pop an empty stack") } case Peek => Future { if (buf.size != 0) StackInfo(Some(buf(0)), buf.size, Max) else throw new IllegalStateException("can't peek an empty stack") } case Size => Future { StackInfo(None, buf.size, Max) } } }
override def toString: String = name }You may want to test the stack represented by the
StackActor
class in different states: empty, full, with one item, with one item less than capacity, etc. You may find you have several tests that make sense any time the stack is non-empty. Thus you'd ideally want to run those same tests for three stack fixture objects: a full stack, a stack with a one item, and a stack with one item less than capacity. With shared tests, you can factor these tests out into a behavior function, into which you pass the stack fixture to use when running the tests. So in yourAsyncFunSpec
forStackActor
, you'd invoke the behavior function three times, passing in each of the three stack fixtures so that the shared tests are run for all three fixtures.You can define a behavior function that encapsulates these shared tests inside the
AsyncWordSpec
that uses them. If they are shared between differentAsyncFunSpec
s, however, you could also define them in a separate trait that is mixed into eachAsyncFunSpec
that uses them. For example, here thenonEmptyStackActor
behavior function (in this case, a behavior method) is defined in a trait along with another method containing shared tests for non-full stacks:import org.scalatest.AsyncWordSpec
trait AsyncWordSpecStackBehaviors { this: AsyncWordSpec =>
def nonEmptyStackActor(createNonEmptyStackActor: => StackActor[Int], lastItemAdded: Int, name: String): Unit = {
("return non-empty StackInfo when Size is fired at non-empty stack actor: " + name) in { val stackActor = createNonEmptyStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(!stackInfo.isEmpty) } }
("return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: " + name) in { val stackActor = createNonEmptyStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePeek <- stackActor ? Size afterPeek <- stackActor ? Peek } yield (beforePeek, afterPeek) futurePair map { case (beforePeek, afterPeek) => assert(afterPeek.top == Some(lastItemAdded)) assert(afterPeek.size == beforePeek.size) } }
("return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: " + name) in { val stackActor = createNonEmptyStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePop <- stackActor ? Size afterPop <- stackActor ? Pop } yield (beforePop, afterPop) futurePair map { case (beforePop, afterPop) => assert(afterPop.top == Some(lastItemAdded)) assert(afterPop.size == beforePop.size - 1) } } }
def nonFullStackActor(createNonFullStackActor: => StackActor[Int], name: String): Unit = {
("return non-full StackInfo when Size is fired at non-full stack actor: " + name) in { val stackActor = createNonFullStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(!stackInfo.isFull) } }
("return before and after StackInfo that has existing size + 1 and new item as top when Push is fired at non-full stack actor: " + name) in { val stackActor = createNonFullStackActor val futurePair: Future[(StackInfo[Int], StackInfo[Int])] = for { beforePush <- stackActor ? Size afterPush <- { stackActor ! Push(7); stackActor ? Peek } } yield (beforePush, afterPush) futurePair map { case (beforePush, afterPush) => assert(afterPush.top == Some(7)) assert(afterPush.size == beforePush.size + 1) } } } }Given these behavior functions, you could invoke them directly, but
AsyncWordSpec
offers a DSL for the purpose, which looks like this:behave like nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName) behave like nonFullStackActor(almostEmptyStackActor, almostEmptyStackActorName)
Here's an example:
class StackSpec extends AsyncWordSpec with AsyncWordSpecStackBehaviors {
val Max = 10 val LastValuePushed = Max - 1
// Stack fixture creation methods val emptyStackActorName = "empty stack actor" def emptyStackActor = new StackActor[Int](Max, emptyStackActorName )
val fullStackActorName = "full stack actor" def fullStackActor = { val stackActor = new StackActor[Int](Max, fullStackActorName ) for (i <- 0 until Max) stackActor ! Push(i) stackActor }
val almostEmptyStackActorName = "almost empty stack actor" def almostEmptyStackActor = { val stackActor = new StackActor[Int](Max, almostEmptyStackActorName ) stackActor ! Push(LastValuePushed) stackActor }
val almostFullStackActorName = "almost full stack actor" def almostFullStackActor = { val stackActor = new StackActor[Int](Max, almostFullStackActorName) for (i <- 1 to LastValuePushed) stackActor ! Push(i) stackActor }
"A Stack" when { "empty" should { "be empty" in { val stackActor = emptyStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(stackInfo.isEmpty) } }
"complain on peek" in { recoverToSucceededIf[IllegalStateException] { emptyStackActor ? Peek } }
"complain on pop" in { recoverToSucceededIf[IllegalStateException] { emptyStackActor ? Pop } } } "non-empty" should { behave like nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName) behave like nonFullStackActor(almostEmptyStackActor, almostEmptyStackActorName) behave like nonEmptyStackActor(almostFullStackActor, LastValuePushed, almostFullStackActorName) behave like nonFullStackActor(almostFullStackActor, almostFullStackActorName) } "full" should { "be full" in { val stackActor = fullStackActor val futureStackInfo = stackActor ? Size futureStackInfo map { stackInfo => assert(stackInfo.isFull) } } behave like nonEmptyStackActor(fullStackActor, LastValuePushed, fullStackActorName) "complain on a push" in { val stackActor = fullStackActor assertThrows[IllegalStateException] { stackActor ! Push(10) } } } } }If you load these classes into the Scala interpreter (with scalatest's JAR file on the class path), and execute it, you'll see:
scala> org.scalatest.run(new StackSpec) StackSpec: A Stack when empty - should be empty - should complain on peek - should complain on pop when non-empty - should return non-empty StackInfo when Size is fired at non-empty stack actor: almost empty stack actor - should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost empty stack actor - should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost empty stack actor - should return non-full StackInfo when Size is fired at non-full stack actor: almost empty stack actor - should return before and after StackInfo that has existing size + 1 and new item as top when Push is fired at non-full stack actor: almost empty stack actor - should return non-empty StackInfo when Size is fired at non-empty stack actor: almost full stack actor - should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost full stack actor - should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost full stack actor - should return non-full StackInfo when Size is fired at non-full stack actor: almost full stack actor - should return before and after StackInfo that has existing size + 1 and new item as top when Push is fired at non-full stack actor: almost full stack actor when full - should be full - should return non-empty StackInfo when Size is fired at non-empty stack actor: full stack actor - should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: full stack actor - should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: full stack actor - should complain on a push
One thing to keep in mind when using shared tests is that in ScalaTest, each test in a suite must have a unique name. If you register the same tests repeatedly in the same suite, one problem you may encounter is an exception at runtime complaining that multiple tests are being registered with the same test name. Although in an
AsyncWordSpec
, thewhen
,should
,can
andmust
clause is a nesting construct analogous toAsyncFunSpec
'sdescribe
clause, you many sometimes need to do a bit of extra work to ensure that the test names are unique. If a duplicate test name problem shows up in anAsyncFunSpec
, you'll need to pass in a prefix or suffix string to add to each test name. You can calltoString
on the shared fixture object, or pass this string the same way you pass any other data needed by the shared tests. This is the approach taken by the previousAsyncFunSpecStackBehaviors
example.Given this
AsyncFunSpecStackBehaviors
trait, calling it with thealmostEmptyStackActor
fixture, like this:behave like nonEmptyStackActor(almostEmptyStackActor, LastValuePushed, almostEmptyStackActorName)
yields test names:
A Stack when non-empty should return non-empty StackInfo when Size is fired at non-empty stack actor: almost empty stack actor
A Stack when non-empty should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost empty stack actor
A Stack when non-empty should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost empty stack actor
Whereas calling it with the
almostFullStackActor
fixture, like this:behave like nonEmptyStackActor(almostFullStackActor, LastValuePushed, almostFullStackActorName)
yields different test names:
A Stack when non-empty should return non-empty StackInfo when Size is fired at non-empty stack actor: almost full stack actor
A Stack when non-empty should return before and after StackInfo that has existing size and lastItemAdded as top when Peek is fired at non-empty stack actor: almost full stack actor
A Stack when non-empty should return before and after StackInfo that has existing size - 1 and lastItemAdded as top when Pop is fired at non-empty stack actor: almost full stack actor
- trait AsyncWordSpecLike extends AsyncTestSuite with AsyncTestRegistration with ShouldVerb with MustVerb with CanVerb with Informing with Notifying with Alerting with Documenting
Implementation trait for class
AsyncWordSpec
, which facilitates a “behavior-driven” style of development (BDD), in which tests are combined with text that specifies the behavior the tests verify.Implementation trait for class
AsyncWordSpec
, which facilitates a “behavior-driven” style of development (BDD), in which tests are combined with text that specifies the behavior the tests verify.AsyncWordSpec
is a class, not a trait, to minimize compile time given there is a slight compiler overhead to mixing in traits compared to extending classes. If you need to mix the behavior ofAsyncWordSpec
into some other class, you can use this trait instead, because classAsyncWordSpec
does nothing more than extend this trait and add a nicetoString
implementation.See the documentation of the class for a detailed overview of
AsyncWordSpec
. - trait BeforeAndAfter extends SuiteMixin
Trait that can be mixed into suites that need code executed before and after running each test.
Trait that can be mixed into suites that need code executed before and after running each test.
Recommended Usage: Use trait BeforeAndAfter
when you need to perform the same side-effects before and/or after tests, rather than at the beginning or end of tests. Note: For more insight into whereBeforeAndAfter
fits into the big picture, see the Shared fixtures section in the documentation for your chosen style trait.A test fixture is composed of the objects and other artifacts (files, sockets, database connections, etc.) tests use to do their work. When multiple tests need to work with the same fixtures, it is important to try and avoid duplicating the fixture code across those tests. The more code duplication you have in your tests, the greater drag the tests will have on refactoring the actual production code. Trait
BeforeAndAfter
offers one way to eliminate such code duplication: abefore
clause that will register code to be run before each test, and anafter
clause that will register code to be run after.Here's an example:
package org.scalatest.examples.flatspec.beforeandafter
import org.scalatest._ import collection.mutable.ListBuffer
class ExampleSpec extends FlatSpec with BeforeAndAfter {
val builder = new StringBuilder val buffer = new ListBuffer[String]
before { builder.append("ScalaTest is ") }
after { builder.clear() buffer.clear() }
"Testing" should "be easy" in { builder.append("easy!") assert(builder.toString === "ScalaTest is easy!") assert(buffer.isEmpty) buffer += "sweet" }
it should "be fun" in { builder.append("fun!") assert(builder.toString === "ScalaTest is fun!") assert(buffer.isEmpty) } }The
before
andafter
methods can each only be called once perSuite
, and cannot be invoked afterrun
has been invoked. If either of the registered before or after functions complete abruptly with an exception, it will be reported as an aborted suite and no more tests will be attempted in that suite.Note that the only way
before
andafter
code can communicate with test code is via some side-effecting mechanism, commonly by reassigning instancevar
s or by changing the state of mutable objects held from instanceval
s (as in this example). If using instancevar
s or mutable objects held from instanceval
s you wouldn't be able to run tests in parallel in the same instance of the test class unless you synchronized access to the shared, mutable state. This is why ScalaTest'sParallelTestExecution
trait extendsOneInstancePerTest
. By running each test in its own instance of the class, each test has its own copy of the instance variables, so you don't need to synchronize. Were you to mixParallelTestExecution
into theExampleSuite
above, the tests would run in parallel just fine without any synchronization needed on the mutableStringBuilder
andListBuffer[String]
objects.Although
BeforeAndAfter
provides a minimal-boilerplate way to execute code before and after tests, it isn't designed to enable stackable traits, because the order of execution would be non-obvious. If you want to factor out before and after code that is common to multiple test suites, you should use traitBeforeAndAfterEach
instead.The advantage this trait has over
BeforeAndAfterEach
is that its syntax is more concise. The main disadvantage is that it is not stackable, whereasBeforeAndAfterEach
is. I.e., you can write several traits that extendBeforeAndAfterEach
and providebeforeEach
methods that include a call tosuper.beforeEach
, and mix them together in various combinations. By contrast, only one call to thebefore
registration function is allowed in a suite or spec that mixes inBeforeAndAfter
. In addition,BeforeAndAfterEach
allows you to access the config map and test name via theTestData
passed to itsbeforeEach
andafterEach
methods, whereasBeforeAndAfter
gives you no access to the config map. - trait BeforeAndAfterAll extends SuiteMixin
Stackable trait that can be mixed into suites that need methods invoked before and after executing the suite.
Stackable trait that can be mixed into suites that need methods invoked before and after executing the suite.
This trait allows code to be executed before and/or after all the tests and nested suites of a suite are run. This trait overrides
run
and calls thebeforeAll
method, then callssuper.run
. After thesuper.run
invocation completes, whether it returns normally or completes abruptly with an exception, this trait'srun
method will invokeafterAll
.Trait
BeforeAndAfterAll
definesbeforeAll
andafterAll
methods that take no parameters. This trait's implementation of these methods do nothing.For example, the following
ExampleSpec
mixes inBeforeAndAfterAll
and inbeforeAll
, creates and writes to a temp file. Each test class,ExampleSpec
and all its nested suites--OneSpec
,TwoSpec
,RedSpec
, andBlueSpec
--tests that the file exists. After all of the nested suites have executed,afterAll
is invoked, which deletes the file. (Note: if you're unfamiliar with thewithFixture(OneArgTest)
approach to shared fixtures, check out the documentation for traitfixture.FlatSpec
.)package org.scalatest.examples.beforeandafterall
import org.scalatest._ import java.io._
trait TempFileExistsSpec extends fixture.FlatSpecLike {
protected val tempFileName = "tmp.txt"
type FixtureParam = File override def withFixture(test: OneArgTest) = { val file = new File(tempFileName) withFixture(test.toNoArgTest(file)) // loan the fixture to the test }
"The temp file" should ("exist in " + suiteName) in { file => assert(file.exists) } }
class OneSpec extends TempFileExistsSpec class TwoSpec extends TempFileExistsSpec class RedSpec extends TempFileExistsSpec class BlueSpec extends TempFileExistsSpec
class ExampleSpec extends Suites( new OneSpec, new TwoSpec, new RedSpec, new BlueSpec ) with TempFileExistsSpec with BeforeAndAfterAll {
// Set up the temp file needed by the test, taking // a file name from the config map override def beforeAll() { val writer = new FileWriter(tempFileName) try writer.write("Hello, suite of tests!") finally writer.close() }
// Delete the temp file override def afterAll() { val file = new File(tempFileName) file.delete() } }If you do supply a mapping for
"tempFileName"
in the config map, you'll see that the temp file is available to all the tests:scala> org.scalatest.run(new ExampleSpec) ExampleSpec: OneSpec: The temp file - should exist in OneSpec TwoSpec: The temp file - should exist in TwoSpec RedSpec: The temp file - should exist in RedSpec BlueSpec: The temp file - should exist in BlueSpec The temp file - should exist in ExampleSpec
Note: this trait uses the
Status
result ofSuite
's "run" methods to ensure that the code inafterAll
is executed after all the tests and nested suites are executed even if aDistributor
is passed.Note that it is not guaranteed that
afterAll
is invoked from the same thread asbeforeAll
, so if there's any shared state betweenbeforeAll
andafterAll
you'll need to make sure they are synchronized correctly. - trait BeforeAndAfterAllConfigMap extends SuiteMixin
Trait that can be mixed into suites that need methods that make use of the config map invoked before and/or after executing the suite.
Trait that can be mixed into suites that need methods that make use of the config map invoked before and/or after executing the suite.
This trait allows code to be executed before and/or after all the tests and nested suites of a suite are run. This trait overrides
run
and calls thebeforeAll(ConfigMap)
method, then callssuper.run
. After thesuper.run
invocation completes, whether it returns normally or completes abruptly with an exception, this trait'srun
method will invokeafterAll(ConfigMap)
.Note that this trait differs from
BeforeAndAfterAll
in that it gives thebeforeAll
andafterAll
code access to the config map. If you don't need the config map, useBeforeAndAfterAll
instead.Trait
BeforeAndAfterAllConfigMap
definesbeforeAll
andafterAll
methods that take aconfigMap
. This trait's implemention of each method does nothing.For example, the following
ExampleSpec
mixes inBeforeAndAfterAllConfigMap
and inbeforeAll
, creates and writes to a temp file, taking the name of the temp file from theconfigMap
. This sameconfigMap
is then passed to therun
methods of the nested suites,OneSpec
,TwoSpec
,RedSpec
, andBlueSpec
, so those suites can access the filename and, therefore, the file's contents. After all of the nested suites have executed,afterAll
is invoked, which again grabs the file name from theconfigMap
and deletes the file. Each of these five test classes extend traitTempFileExistsSpec
, which defines a test that ensures the temp file exists. (Note: if you're unfamiliar with thewithFixture(OneArgTest)
approach to shared fixtures, check out the documentation for traitfixture.FlatSpec
.)package org.scalatest.examples.beforeandafterallconfigmap
import org.scalatest._ import java.io._
trait TempFileExistsSpec extends fixture.FlatSpec {
type FixtureParam = File override def withFixture(test: OneArgTest) = { val fileName = test.configMap.getRequired[String]("tempFileName") val file = new File(fileName) withFixture(test.toNoArgTest(file)) // loan the fixture to the test }
"The temp file" should ("exist in " + suiteName) in { file => assert(file.exists) } }
class OneSpec extends TempFileExistsSpec class TwoSpec extends TempFileExistsSpec class RedSpec extends TempFileExistsSpec class BlueSpec extends TempFileExistsSpec
class ExampleSpec extends Suites( new OneSpec, new TwoSpec, new RedSpec, new BlueSpec ) with TempFileExistsSpec with BeforeAndAfterAllConfigMap {
private val tempFileName = "tempFileName"
// Set up the temp file needed by the test, taking // a file name from the config map override def beforeAll(cm: ConfigMap) { assume( cm.isDefinedAt(tempFileName), "must place a temp file name in the config map under the key: " + tempFileName ) val fileName = cm.getRequired[String](tempFileName) val writer = new FileWriter(fileName) try writer.write("Hello, suite of tests!") finally writer.close() }
// Delete the temp file override def afterAll(cm: ConfigMap) { val fileName = cm.getRequired[String]("tempFileName") val file = new File(fileName) file.delete() } }Running the above class in the interpreter will give an error if you don't supply a mapping for
"tempFileName"
in the config map:scala> org.scalatest.run(new ExampleSpec) ExampleSpec: Exception encountered when invoking run on a suite. *** ABORTED *** Exception encountered when invoking run on a suite. (
:30) *** RUN ABORTED *** An exception or error caused a run to abort: must place a temp file name in the config map under the key: tempFileName ( :30) If you do supply a mapping for
"tempFileName"
in the config map, you'll see that the temp file is available to all the tests:scala> (new ExampleSpec).execute(configMap = ConfigMap("tempFileName" -> "tmp.txt")) ExampleSpec: OneSpec: The temp file - should exist in OneSpec TwoSpec: The temp file - should exist in TwoSpec RedSpec: The temp file - should exist in RedSpec BlueSpec: The temp file - should exist in BlueSpec The temp file - should exist in ExampleSpec
Note: As of 2.0.M5, this trait uses the newly added
Status
result ofSuite
's "run" methods to ensure that the code inafterAll
is executed after all the tests and nested suites are executed even if aDistributor
is passed.Note that it is not guaranteed that
afterAll
is invoked from the same thread asbeforeAll
, so if there's any shared state betweenbeforeAll
andafterAll
you'll need to make sure they are synchronized correctly. - trait BeforeAndAfterEach extends SuiteMixin
Stackable trait that can be mixed into suites that need code executed before and/or after running each test.
Stackable trait that can be mixed into suites that need code executed before and/or after running each test.
Recommended Usage: Use trait BeforeAndAfterEach
when you want to stack traits that perform side-effects before and/or after tests, rather than at the beginning or end of tests. Note: For more insight into whereBeforeAndAfterEach
fits into the big picture, see the Shared fixtures section in the documentation for your chosen style trait.A test fixture is composed of the objects and other artifacts (files, sockets, database connections, etc.) tests use to do their work. When multiple tests need to work with the same fixtures, it is important to try and avoid duplicating the fixture code across those tests. The more code duplication you have in your tests, the greater drag the tests will have on refactoring the actual production code, and the slower your compile will likely be. Trait
BeforeAndAfterEach
offers one way to eliminate such code duplication: abeforeEach
method that will be run before each test (like JUnit'ssetUp
), and anafterEach
method that will be run after (like JUnit'stearDown
).Here's an example:
package org.scalatest.examples.flatspec.composingbeforeandaftereach
import org.scalatest._ import collection.mutable.ListBuffer
trait Builder extends BeforeAndAfterEach { this: Suite =>
val builder = new StringBuilder
override def beforeEach() { builder.append("ScalaTest is ") super.beforeEach() // To be stackable, must call super.beforeEach }
override def afterEach() { try { super.afterEach() // To be stackable, must call super.afterEach } finally { builder.clear() } } }
trait Buffer extends BeforeAndAfterEach { this: Suite =>
val buffer = new ListBuffer[String]
override def afterEach() { try { super.afterEach() // To be stackable, must call super.afterEach } finally { buffer.clear() } } }
class ExampleSpec extends FlatSpec with Builder with Buffer {
"Testing" should "be easy" in { builder.append("easy!") assert(builder.toString === "ScalaTest is easy!") assert(buffer.isEmpty) buffer += "sweet" }
it should "be fun" in { builder.append("fun!") assert(builder.toString === "ScalaTest is fun!") assert(buffer.isEmpty) buffer += "clear" } }To get the same ordering as
withFixture
, place yoursuper.beforeEach
call at the end of eachbeforeEach
method, and thesuper.afterEach
call at the beginning of eachafterEach
method, as shown in the previous example. It is a good idea to invokesuper.afterEach
in atry
block and perform cleanup in afinally
clause, as shown in the previous example, because this ensures the cleanup code is performed even ifsuper.afterEach
throws an exception.The main advantage of
BeforeAndAfterEach
overBeforeAndAfter
is thatBeforeAndAfterEach
. enables trait stacking. The main disadvantage ofBeforeAndAfterEach
compared toBeforeAndAfter
is thatBeforeAndAfterEach
requires more boilerplate. If you don't need trait stacking, useBeforeAndAfter
instead ofBeforeAndAfterEach
. If you want to make use of test data (the test name, config map, etc.) in yourbeforeEach
orafterEach
method, use traitBeforeAndAfterEachTestData
instead. - trait BeforeAndAfterEachTestData extends SuiteMixin
Stackable trait that can be mixed into suites that need code that makes use of test data (test name, tags, config map, etc.) executed before and/or after running each test.
Stackable trait that can be mixed into suites that need code that makes use of test data (test name, tags, config map, etc.) executed before and/or after running each test.
Recommended Usage: Use trait BeforeAndAfterEachTestData
when you want to stack traits that perform side-effects before and/or after tests, rather than at the beginning or end of tests, when you need access to any test data (such as the config map) in the before and/or after code. Note: For more insight into whereBeforeAndAfterEachTestData
fits into the big picture, see the Shared fixtures section in the documentation for your chosen style trait.A test fixture is composed of the objects and other artifacts (files, sockets, database connections, etc.) tests use to do their work. When multiple tests need to work with the same fixtures, it is important to try and avoid duplicating the fixture code across those tests. The more code duplication you have in your tests, the greater drag the tests will have on refactoring the actual production code. Trait
BeforeAndAfterEachTestData
offers one way to eliminate such code duplication: abeforeEach(TestData)
method that will be run before each test (like JUnit'ssetUp
), and anafterEach(TestData)
method that will be run after (like JUnit'stearDown
).Here's an example:
package org.scalatest.examples.flatspec.composingbeforeandaftereachtestdata
import org.scalatest._ import collection.mutable.ListBuffer
trait Builder extends BeforeAndAfterEachTestData { this: Suite =>
val builder = new StringBuilder
override def beforeEach(td: TestData) { builder.append(td.name) super.beforeEach(td) // To be stackable, must call super.beforeEach(TestData) }
override def afterEach(td: TestData) { try { super.afterEach(td) // To be stackable, must call super.afterEach(TestData) } finally { builder.clear() } } }
trait Buffer extends BeforeAndAfterEachTestData { this: Suite =>
val buffer = new ListBuffer[String]
override def afterEach(td: TestData) { try { super.afterEach(td) // To be stackable, must call super.afterEach(TestData) } finally { buffer.clear() } } }
class ExampleSpec extends FlatSpec with Builder with Buffer {
"Testing" should "be easy" in { builder.append("!") assert(builder.toString === "Testing should be easy!") assert(buffer.isEmpty) buffer += "sweet" }
it should "be fun" in { builder.append("!") assert(builder.toString === "Testing should be fun!") assert(buffer.isEmpty) buffer += "clear" } }To get the same ordering as
withFixture
, place yoursuper.beforeEach(TestData)
call at the end of eachbeforeEach(TestData)
method, and thesuper.afterEach(TestData)
call at the beginning of eachafterEach(TestData)
method, as shown in the previous example. It is a good idea to invokesuper.afterEach(TestData)
in atry
block and perform cleanup in afinally
clause, as shown in the previous example, because this ensures the cleanup code is performed even ifsuper.afterEach(TestData)
throws an exception.Besides enabling trait stacking, the other main advantage of
BeforeAndAfterEachTestData
overBeforeAndAfter
is thatBeforeAndAfterEachTestData
allows you to make use of test data (such as the test name and config map) in your before and/or after code, whereasBeforeAndAfter
does not.The main disadvantage of
BeforeAndAfterEachTestData
compared toBeforeAndAfter
andBeforeAndAfterEach
is thatBeforeAndAfterEachTestData
requires more boilerplate. If you don't need trait stacking or access to the test data, useBeforeAndAfter
instead ofBeforeAndAfterEachTestData
. If you need trait stacking, but not access to theTestData
, useBeforeAndAfterEach
instead. - trait CancelAfterFailure extends TestSuiteMixin
Trait that when mixed into a
TestSuite
cancels any remaining tests in thatTestSuite
instance after a test fails.Trait that when mixed into a
TestSuite
cancels any remaining tests in thatTestSuite
instance after a test fails.The intended use case for this trait is if you have a suite of long-running tests that are related such that if one fails, you aren't interested in running the others, you can use this trait to simply cancel any remaining tests, so you need not wait long for them to complete.
Note that this trait only cancels tests in the same
TestSuite
instance, because it uses a private volatile instance variable as a flag to indicate whether or not a test has failed. If you are running each test in its own instance, therefore, it would not cancel the remaining tests, because they would not see the same flag. For this reason, this trait contains a final implementation of a method defined inOneInstancePerTest
, to prevent it from being mixed into any class that also mixes inOneInstancePerTest
, including by mixing inParallelTestExecution
or a path trait. - case class Canceled(exception: TestCanceledException) extends Exceptional with Product with Serializable
Outcome for a test that was canceled, containing an exception describing the cause of the cancelation.
- trait Checkpoints extends AnyRef
Trait providing class
Checkpoint
, which enables multiple assertions to be performed within a test, with any failures accumulated and reported together at the end of the test.Trait providing class
Checkpoint
, which enables multiple assertions to be performed within a test, with any failures accumulated and reported together at the end of the test.Because ScalaTest uses exceptions to signal failed assertions, normally execution of a test will stop as soon as the first failed assertion is encountered. Trait
Checkpoints
provides an option when you want to continue executing the remainder of the test body, or part of it, even if an assertion has already failed in that test.To use a
Checkpoint
(once you've mixed in or imported the members of traitCheckpoints
), you first need to create one, like this:val cp = new Checkpoint
Then give the
Checkpoint
assertions to execute by passing them (via a by-name parameter) to itsapply
method, like this:val (x, y) = (1, 2) cp { x should be < 0 } cp { y should be > 9 }
Both of the above assertions will fail, but it won't be reported yet. The
Checkpoint
will execute them right away, each time itsapply
method is invoked. But it will catch theTestFailedExceptions
and save them, only reporting them later whenreportAll
is invoked. Thus, at the end of the test, you must callreportAll
, like this:cp.reportAll()
This
reportAll
invocation will complete abruptly with aTestFailedException
whose message includes the message, source file, and line number of each of the checkpointed assertions that previously failed. For example:1 was not less than 0 (in Checkpoint) at ExampleSpec.scala:12 2 was not greater than 9 (in Checkpoint) at ExampleSpec.scala:13
Make sure you invoke
reportAll
before the test completes, otherwise any failures that were detected by theCheckpoint
will not be reported.Note that a
Checkpoint
will catch and record for later reporting (viareportAll
) exceptions that mix inStackDepth
except forTestCanceledException
,TestRegistrationClosedException
,NotAllowedException
, andDuplicateTestNameException
. If a block of code passed to aCheckpoint
'sapply
method completes abruptly with any of theStackDepth
exceptions in the previous list, or any non-StackDepth
exception, that invocation of theapply
method will complete abruptly with the same exception immediately. Unless you putreportAll
in a finally clause and handle this case, such an unexpected exception will cause you to lose any information about assertions that failed earlier in the test and were recorded by theCheckpoint
. - trait CompleteLastly extends AnyRef
Trait that provides a
complete
-lastly
construct, which ensures cleanup code inlastly
is executed whether the code passed tocomplete
completes abruptly with an exception or successfully results in aFuture
,FutureOutcome
, or other type with an implicitFuturistic
instance.Trait that provides a
complete
-lastly
construct, which ensures cleanup code inlastly
is executed whether the code passed tocomplete
completes abruptly with an exception or successfully results in aFuture
,FutureOutcome
, or other type with an implicitFuturistic
instance.This trait is mixed into ScalaTest's async testing styles, to make it easy to ensure cleanup code will execute whether code that produces a "futuristic" value (any type
F
for which aFuturistic[F]
instance is implicitly available). ScalaTest provides implicitFuturistic
instances forFuture[T]
for any typeT
andFutureOutcome
.If the future-producing code passed to
complete
throws an exception, the cleanup code passed tolastly
will be executed immediately, and the same exception will be rethrown, unless the code passed tolastly
also completes abruptly with an exception. In that case,complete
-lastly
will complete abruptly with the exception thrown by the code passed tolastly
(this mimics the behavior offinally
).Otherwise, if the code passed to
complete
successfully returns aFuture
(or other "futuristic" type),complete
-lastly
will register the cleanup code to be performed once the future completes and return a new future that will complete once the original future completes and the subsequent cleanup code has completed execution. The future returned bycomplete
-lastly
will have the same result as the original future passed tocomplete
, unless the cleanup code throws an exception. If the cleanup code passed tolastly
throws an exception, the future returned bylastly
will fail with that exception.The
complete
-lastly
syntax is intended to be used to ensure cleanup code is executed in async testing styles liketry
-finally
is used in traditional testing styles. Here's an example ofcomplete
-lastly
used inwithFixture
in an async testing style:// Your implementation override def withFixture(test: NoArgAsyncTest) = {
// Perform setup here
complete { super.withFixture(test) // Invoke the test function } lastly { // Perform cleanup here } } - final class CompositeStatus extends Status with Serializable
Composite
Status
that aggregates its completion and failed states of set of otherStatus
es passed to its constructor. - class ConfigMap extends Map[String, Any] with Serializable
A map of configuration data.
A map of configuration data.
A
ConfigMap
can be populated from theRunner
command line via-D
arguments.Runner
passes it to many methods where you can use it to configure your test runs. For example,Runner
passed theConfigMap
to:- the
apply
method ofReporter
s viaRunStarting
events - the
run
method ofSuite
- the
runNestedSuites
method ofSuite
- the
runTests
method ofSuite
- the
runTest
method ofSuite
- the
withFixture(NoArgTest)
method ofSuite
- the
withFixture(OneArgTest)
method offixture.Suite
- the
beforeEach(TestData)
method ofBeforeAndAfterEachTestData
- the
afterEach(TestData)
method ofBeforeAndAfterEachTestData
In addition to accessing the
ConfigMap
in overriden implementations of the above methods, you can also transform and pass along a modifiedConfigMap
.A
ConfigMap
maps string keys to values of any type, i.e., it is aMap[String, Any]
. To get a configuration value in a variable of the actual type of that value, therefore, you'll need to perform an unsafe cast. If this cast fails, you'll get an exception, which so long as theConfigMap
is used only in tests, will result in either a failed or canceled test or aborted suite. To give such exceptions nice stack depths and error messages, and to eliminate the need for usingasInstanceOf
in your test code,ConfigMap
provides three methods for accessing values at expected types.The
getRequired
method returns the value bound to a key cast to a specified type, or throwsTestCanceledException
if either the key is not bound or is bound to an incompatible type. Here's an example:val tempFileName: String = configMap.getRequired[String]("tempFileName")
The
getOptional
method returns the value bound to a key cast to a specified type, wrapped in aSome
, returnsNone
if the key is not bound, or throws TestCanceledException if the key exists but is bound to an incompatible type. Here's an example:val tempFileName: Option[String] = configMap.getOptional[String]("tempFileName")
The
getWithDefault
method returns the value bound to a key cast to a specified type, returns a specified default value if the key is not bound, or throws TestCanceledException if the key exists but is either not bound or is bound to an incompatible type. Here's an example:val tempFileName: String = configMap.getWithDefault[String]("tempFileName", "tmp.txt")
- the
- final class ConfigMapWrapperSuite extends Suite
Wrapper
Suite
that passes an instance of the config map to the constructor of the wrappedSuite
whenrun
is invoked.Wrapper
Suite
that passes an instance of the config map to the constructor of the wrappedSuite
whenrun
is invoked.Recommended Usage: Trait ConfigMapWrapperSuite
is primarily intended to be used with the "path" traits, which can't use the usual approaches to accessing the config map because of the eager manner in which they run tests.Each time
run
is invoked on an instance ofConfigMapWrapperSuite
, this suite will create a new instance of the suite to wrap, passing to the constructor the config map passed torun
. This way, if the sameConfigMapWrapperSuite
instance is run multiple times, each time with a different config map, an instance of the wrapped suite will be created for each config map. In addition to being passed to the wrapped suite's constructor, the config map passed to theConfigMapWrapperSuite
'srun
method will also be passed to therun
method of the newly created wrapped suite instance.The config map is accessible inside a
Suite
in many ways. It is passed torun
,runNestedSuites
,runTests
, andrunTest
. It is also passed towithFixture
, accessible via a method onNoArgTest
andOneArgTest
. It is passed to an overloaded forms of thebeforeEach
andafterEach
methods of traitBeforeAndAfterEach
, as well as overloaded forms of thebeforeAll
andafterAll
methods of traitBeforeAndAfterAll
. Tests themselves can have information taken from the config map, or the entire config map, through various means. The config map may be passed into the test via aConfigMapFixture
, for example. ClassConfigMapWrapperSuite
represents one more way to get at the config map inside a suite of test:ConfigMapWrapperSuite
will pass the config map to the constructor of your suite class, bringing it easily into scope for tests and helper methods alike.Having the config map passed to the suite constructor might be more convenient in some cases, but in the case of the
org.scalatest.path
traits, it is necessary if a test needs information from a config map. The reason is that in a path trait, the test code is executed eagerly, beforerun
is invoked. The results of the tests are registered when the tests are executed, and those results are merely reported oncerun
is invoked. Thus by the timerun
has been invoked, it is too late to get the config map to the tests, which have already been executed. Using aConfigMapWrapperSuite
solves that problem. By passing the config map to the constructor, it is available early enough for the running tests to use it. Here's an example:import org.scalatest._
@WrapWith(classOf[ConfigMapWrapperSuite]) class ExampleSpec(configMap: ConfigMap) extends path.FunSpec {
describe("A widget database") { it("should contain consistent values") { val dbName = configMap("WidgetDbName") // Can access config map // ... } } } - trait DiagrammedAssertions extends Assertions
Sub-trait of
Assertions
that overrideassert
andassume
methods to include a diagram showing the values of expression in the error message when the assertion or assumption fails.Sub-trait of
Assertions
that overrideassert
andassume
methods to include a diagram showing the values of expression in the error message when the assertion or assumption fails.Here are some examples:
scala> import DiagrammedAssertions._ import DiagrammedAssertions._ scala> assert(a == b || c >= d) org.scalatest.exceptions.TestFailedException: assert(a == b || c >= d) | | | | | | | 1 | 2 | 3 | 4 | | false | false false at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:422) ... scala> assert(xs.exists(_ == 4)) org.scalatest.exceptions.TestFailedException: assert(xs.exists(_ == 4)) | | | false List(1, 2, 3) at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:422) ... scala> assert("hello".startsWith("h") && "goodbye".endsWith("y")) org.scalatest.exceptions.TestFailedException: assert("hello".startsWith("h") && "goodbye".endsWith("y")) | | | | | | | "hello" true "h" | "goodbye" false "y" false at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:422) ... scala> assert(num.isInstanceOf[Int]) org.scalatest.exceptions.TestFailedException: assert(num.isInstanceOf[Int]) | | 1.0 false at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:422) ... scala> assert(Some(2).isEmpty) org.scalatest.exceptions.TestFailedException: assert(Some(2).isEmpty) | | | | 2 false Some(2) at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:422) ... scala> assert(None.isDefined) org.scalatest.exceptions.TestFailedException: assert(None.isDefined) | | None false at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:422) ... scala> assert(xs.exists(i => i > 10)) org.scalatest.exceptions.TestFailedException: assert(xs.exists(i => i > 10)) | | | false List(1, 2, 3) at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:422) ...
If the expression passed to
assert
orassume
spans more than one line,DiagrammedAssertions
falls back to the default style of error message, since drawing a diagram would be difficult. Here's an example showing howDiagrammedAssertions
will treat a multi-line assertion (i.e., you don't get a diagram):scala> assert("hello".startsWith("h") && | "goodbye".endsWith("y")) org.scalatest.exceptions.TestFailedException: "hello" started with "h", but "goodbye" did not end with "y" at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:422) ...
Also, since an expression diagram essentially represents multi-line ascii art, if a clue string is provided, it appears above the diagram, not after it. It will often also show up in the diagram:
scala> assert(None.isDefined, "Don't do this at home") org.scalatest.exceptions.TestFailedException: Don't do this at home assert(None.isDefined, "Don't do this at home") | | None false at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:422) ... scala> assert(None.isDefined, | "Don't do this at home") org.scalatest.exceptions.TestFailedException: Don't do this at home assert(None.isDefined, | | None false at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:422) ...
Trait
DiagrammedAssertions
was inspired by Peter Niederwieser's work in Spock and Expecty. - trait DiagrammedExpr[T] extends AnyRef
A trait that represent an expression recorded by
DiagrammedExprMacro
, which includes the following members:A trait that represent an expression recorded by
DiagrammedExprMacro
, which includes the following members:- a boolean value
- an anchor that records the position of this expression
- anchor values of this expression (including sub-expressions)
DiagrammedExpr
is used by code generated fromDiagrammedAssertionsMacro
, it needs to be public so that the generated code can be compiled. It is expected that ScalaTest users would ever need to useDiagrammedExpr
directly. - trait DistributedSuiteSorter extends AnyRef
A sorter for the events of a run's distributed suites.
A sorter for the events of a run's distributed suites.
This trait is used, for example, when
-PS
is passed toRunner
, to sort the events of distributed suites such that each suite's events are propagated together, with a timeout if an event takes too long. - trait DistributedTestSorter extends AnyRef
A sorter for the events of a suite's distributed tests.
A sorter for the events of a suite's distributed tests.
This trait is used, for example, by
ParallelTestExecution
to sort the events of tests back into sequential order, with a timeout if an event takes too long. - trait Distributor extends AnyRef
Trait whose instances facilitate parallel execution of
Suite
s.Trait whose instances facilitate parallel execution of
Suite
s. An optionalDistributor
is passed to therun
method ofSuite
. If aDistributor
is indeed passed, traitSuite
's implementation ofrun
will populate thatDistributor
with its nestedSuite
s (by passing them to theDistributor
'sapply
method) rather than executing the nestedSuite
s directly. It is then up to another thread or process to execute thoseSuite
s.If you have a set of nested
Suite
s that must be executed sequentially, you can mix in traitSequentialNestedSuiteExecution
, which overridesrunNestedSuites
and callssuper
'srunNestedSuites
implementation, passing inNone
for theDistributor
.Implementations of this trait must be thread safe.
- trait DoNotDiscover extends Annotation
Annotation used to indicate that an otherwise discoverable test class should not be discovered.
Annotation used to indicate that an otherwise discoverable test class should not be discovered.
Note: This is actually an annotation defined in Java, not a Scala trait. It must be defined in Java instead of Scala so it will be accessible at runtime. It has been inserted into Scaladoc by pretending it is a trait.
ScalaTest will discover any class that either extends
Suite
and has a public, no-arg constructor, or is annotated with a validWrapWith
annotation. If you wish to prevent a class from being discovered, simply annotate it withDoNotDiscover
, like this:import org.scalatest._ @DoNotDiscover class SetSpec extends FlatSpec { "An empty Set" should "have size 0" in { assert(Set.empty.size === 0) } it should "produce NoSuchElementException when head is invoked" in { intercept[NoSuchElementException] { Set.empty.head } } }
ScalaTest will run classes annotated with
DoNotDiscover
if asked to explicitly, it just won't discover them.Note that because reflection is not supported on Scala.js, this annotation will only work on the JVM, not on Scala.js.
- trait Documenter extends AnyRef
Trait to which markup text tests can be reported.
Trait to which markup text tests can be reported.
Note:
Documenter
will be described in more detail in a future 2.0 milestone release. As of this release you can't see its effects yet. - trait Documenting extends AnyRef
Trait that contains a
markup
method, which can be used to send markup to theReporter
. - final case class DynaTags(suiteTags: Map[String, Set[String]], testTags: Map[String, Map[String, Set[String]]]) extends Serializable with Product
Dynamic tags for a run.
Dynamic tags for a run.
Instances of this class are passed to the
Filter
constructor to support running selected suites and tests via dynamic tagging. For example, dynamic tags can be used to rerun tests that failed previously, or tests selected via a wildcard fromRunner
or the Scala interpreter.- suiteTags
a map from String suite ID to a set of tags for that suite.
- testTags
a map from String suite ID to a map, whose keys are test names and values the tags for that test.
- Exceptions thrown
NullPointerException
if eithersuiteTags
ortestTags
isnull
- trait EitherValues extends AnyRef
Trait that provides an implicit conversion that adds
left.value
andright.value
methods toEither
, which will return the selected value of theEither
if defined, or throwTestFailedException
if not.Trait that provides an implicit conversion that adds
left.value
andright.value
methods toEither
, which will return the selected value of theEither
if defined, or throwTestFailedException
if not.This construct allows you to express in one statement that an
Either
should be left or right and that its value should meet some expectation. Here's are some examples:either1.right.value should be > 9 either2.left.value should be ("Muchas problemas")
Or, using assertions instead of matcher expressions:
assert(either1.right.value > 9) assert(either2.left.value === "Muchas problemas")
Were you to simply invoke
right.get
orleft.get
on theEither
, if theEither
wasn't defined as expected (e.g., it was aLeft
when you expected aRight
), it would throw aNoSuchElementException
:val either: Either[String, Int] = Left("Muchas problemas")
either.right.get should be > 9 // either.right.get throws NoSuchElementExceptionThe
NoSuchElementException
would cause the test to fail, but without providing a stack depth pointing to the failing line of test code. This stack depth, provided byTestFailedException
(and a few other ScalaTest exceptions), makes it quicker for users to navigate to the cause of the failure. WithoutEitherValues
, to get a stack depth exception you would need to make two statements, like this:val either: Either[String, Int] = Left("Muchas problemas")
either should be ('right) // throws TestFailedException either.right.get should be > 9The
EitherValues
trait allows you to state that more concisely:val either: Either[String, Int] = Left("Muchas problemas")
either.right.value should be > 9 // either.right.value throws TestFailedException - case class Entry[K, V](key: K, value: V) extends java.util.Map.Entry[K, V] with Product with Serializable
A case class implementation of
java.util.Map.Entry
to make it easier to test JavaMap
s with ScalaTest Matchers.A case class implementation of
java.util.Map.Entry
to make it easier to test JavaMap
s with ScalaTest Matchers.In Java,
java.util.Map
is not a subtype ofjava.util.Collection
, and does not actually define an element type. You can ask a JavaMap
for an “entry set” via theentrySet
method, which will return theMap
's key/value pairs wrapped in a set ofjava.util.Map.Entry
, but aMap
is not actually a collection ofEntry
. To make JavaMap
s easier to work with, however, ScalaTest matchers allows you to treat a JavaMap
as a collection ofEntry
, and defines this convenience implementation ofjava.util.Map.Entry
. Here's how you use it:javaMap should contain (Entry(2, 3)) javaMap should contain oneOf (Entry(2, 3), Entry(3, 4))
- key
the key of this entry
- value
the value of this entry
- sealed abstract class Exceptional extends Outcome
Superclass for the two outcomes of running a test that contain an exception:
Failed
andCanceled
.Superclass for the two outcomes of running a test that contain an exception:
Failed
andCanceled
.This class provides a
toOption
method that returns aSome
wrapping the contained exception, and anisExceptional
field with the valuetrue
. It's companion object provides an extractor that enables patterns that match a test that either failed or canceled, as in:outcome match { case Exceptional(ex) => // handle failed or canceled case case _ => // handle succeeded, pending, or omitted case }
- case class Failed(exception: Throwable) extends Exceptional with Product with Serializable
Outcome for a test that failed, containing an exception describing the cause of the failure.
Outcome for a test that failed, containing an exception describing the cause of the failure.
Note: the difference between this
Failed
class and the similarly namedFailedStatus
object is that an instance of this class indicates one test failed, whereas theFailedStatus
object indicates either one or more tests failed and/or one or more suites aborted during a run. Both are used as the result type ofSuite
lifecycle methods, butFailed
is a possible result ofwithFixture
, whereasFailedStatus
is a possible result ofrun
,runNestedSuites
,runTests
, orrunTest
. In short,Failed
is always just about one test, whereasFailedStatus
could be about something larger: multiple tests or an entire suite. - class FeatureSpec extends FeatureSpecLike
A suite of tests in which each test represents one scenario of a feature.
A suite of tests in which each test represents one scenario of a feature.
FeatureSpec
is intended for writing tests that are "higher level" than unit tests, for example, integration tests, functional tests, and acceptance tests. You can useFeatureSpec
for unit testing if you prefer, however.Recommended Usage: Class FeatureSpec
is primarily intended for acceptance testing, including facilitating the process of programmers working alongside non-programmers to define the acceptance requirements.Although not required,
FeatureSpec
is often used together withGivenWhenThen
to express acceptance requirements in more detail. Here's an example:package org.scalatest.examples.featurespec
import org.scalatest._
class TVSet { private var on: Boolean = false def isOn: Boolean = on def pressPowerButton() { on = !on } }
class TVSetSpec extends FeatureSpec with GivenWhenThen {
info("As a TV set owner") info("I want to be able to turn the TV on and off") info("So I can watch TV when I want") info("And save energy when I'm not watching TV")
feature("TV power button") { scenario("User presses power button when TV is off") {
Given("a TV set that is switched off") val tv = new TVSet assert(!tv.isOn)
When("the power button is pressed") tv.pressPowerButton()
Then("the TV should switch on") assert(tv.isOn) }
scenario("User presses power button when TV is on") {
Given("a TV set that is switched on") val tv = new TVSet tv.pressPowerButton() assert(tv.isOn)
When("the power button is pressed") tv.pressPowerButton()
Then("the TV should switch off") assert(!tv.isOn) } } }Note: for more information on the calls to
Given
,When
, andThen
, see the documentation for traitGivenWhenThen
and theInformers
section below.A
FeatureSpec
contains feature clauses and scenarios. You define a feature clause withfeature
, and a scenario withscenario
. Bothfeature
andscenario
are methods, defined inFeatureSpec
, which will be invoked by the primary constructor ofTVSetSpec
. A feature clause describes a feature of the subject (class or other entity) you are specifying and testing. In the previous example, the subject under specification and test is a TV set. The feature being specified and tested is the behavior of a TV set when its power button is pressed. With each scenario you provide a string (the spec text) that specifies the behavior of the subject for one scenario in which the feature may be used, and a block of code that tests that behavior. You place the spec text between the parentheses, followed by the test code between curly braces. The test code will be wrapped up as a function passed as a by-name parameter toscenario
, which will register the test for later execution.A
FeatureSpec
's lifecycle has two phases: the registration phase and the ready phase. It starts in registration phase and enters ready phase the first timerun
is called on it. It then remains in ready phase for the remainder of its lifetime.Scenarios can only be registered with the
scenario
method while theFeatureSpec
is in its registration phase. Any attempt to register a scenario after theFeatureSpec
has entered its ready phase, i.e., afterrun
has been invoked on theFeatureSpec
, will be met with a thrownTestRegistrationClosedException
. The recommended style of usingFeatureSpec
is to register tests during object construction as is done in all the examples shown here. If you keep to the recommended style, you should never see aTestRegistrationClosedException
.Each scenario represents one test. The name of the test is the spec text passed to the
scenario
method. The feature name does not appear as part of the test name. In aFeatureSpec
, therefore, you must take care to ensure that each test has a unique name (in other words, that eachscenario
has unique spec text).When you run a
FeatureSpec
, it will sendFormatter
s in the events it sends to theReporter
. ScalaTest's built-in reporters will report these events in such a way that the output is easy to read as an informal specification of the subject being tested. For example, were you to runTVSetSpec
from within the Scala interpreter:scala> org.scalatest.run(new TVSetSpec)
You would see:
TVSetSpec: As a TV set owner I want to be able to turn the TV on and off So I can watch TV when I want And save energy when I'm not watching TV Feature: TV power button Scenario: User presses power button when TV is off Given a TV set that is switched off When the power button is pressed Then the TV should switch on Scenario: User presses power button when TV is on Given a TV set that is switched on When the power button is pressed Then the TV should switch off
Or, to run just the “
Feature: TV power button Scenario: User presses power button when TV is on
” method, you could pass that test's name, or any unique substring of the name, such as"TV is on"
. Here's an example:scala> org.scalatest.run(new TVSetSpec, "TV is on") TVSetSpec: As a TV set owner I want to be able to turn the TV on and off So I can watch TV when I want And save energy when I'm not watching TV Feature: TV power button Scenario: User presses power button when TV is on Given a TV set that is switched on When the power button is pressed Then the TV should switch off
Note: Trait
FeatureSpec
's syntax is in part inspired by Cucumber, a Ruby BDD framework.Ignored tests
To support the common use case of temporarily disabling a test, with the good intention of resurrecting the test at a later time,
FeatureSpec
provides registration methods that start withignore
instead ofscenario
. For example, to temporarily disable the test namedaddition
, just change “scenario
” into “ignore
,” like this:package org.scalatest.examples.featurespec.ignore
import org.scalatest.FeatureSpec
class TVSet { private var on: Boolean = false def isOn: Boolean = on def pressPowerButton() { on = !on } }
class TVSetSpec extends FeatureSpec {
feature("TV power button") { ignore("User presses power button when TV is off") { val tv = new TVSet assert(!tv.isOn) tv.pressPowerButton() assert(tv.isOn) }
scenario("User presses power button when TV is on") { val tv = new TVSet tv.pressPowerButton() assert(tv.isOn) tv.pressPowerButton() assert(!tv.isOn) } } }If you run this version of
SetSpec
with:scala> org.scalatest.run(new TVSetSpec)
It will run only the second scenario and report that the first scenario was ignored:
TVSetSpec: Feature: TV power button Scenario: User presses power button when TV is off !!! IGNORED !!! Scenario: User presses power button when TV is on
Informers
One of the parameters to
FeatureSpec
'srun
method is aReporter
, which will collect and report information about the running suite of tests. Information about suites and tests that were run, whether tests succeeded or failed, and tests that were ignored will be passed to theReporter
as the suite runs. Most often the default reporting done byFeatureSpec
's methods will be sufficient, but occasionally you may wish to provide custom information to theReporter
from a test. For this purpose, anInformer
that will forward information to the currentReporter
is provided via theinfo
parameterless method. You can pass the extra information to theInformer
via itsapply
method. TheInformer
will then pass the information to theReporter
via anInfoProvided
event.One use case for the
Informer
is to pass more information about a scenario to the reporter. For example, theGivenWhenThen
trait provides methods that use the implicitinfo
provided byFeatureSpec
to pass such information to the reporter. You can see this in action in the initial example of this trait's documentation.Documenters
FeatureSpec
also provides amarkup
method that returns aDocumenter
, which allows you to send to theReporter
text formatted in Markdown syntax. You can pass the extra information to theDocumenter
via itsapply
method. TheDocumenter
will then pass the information to theReporter
via anMarkupProvided
event.Here's an example
FlatSpec
that usesmarkup
:package org.scalatest.examples.featurespec.markup
import collection.mutable import org.scalatest._
class SetSpec extends FeatureSpec with GivenWhenThen {
markup { """ Mutable Set ———-- A set is a collection that contains no duplicate elements. To implement a concrete mutable set, you need to provide implementations of the following methods: def contains(elem: A): Boolean def iterator: Iterator[A] def += (elem: A): this.type def -= (elem: A): this.type If you wish that methods like `take`, `drop`, `filter` return the same kind of set, you should also override: def empty: This It is also good idea to override methods `foreach` and `size` for efficiency. """ }
feature("An element can be added to an empty mutable Set") { scenario("When an element is added to an empty mutable Set") { Given("an empty mutable Set") val set = mutable.Set.empty[String]
When("an element is added") set += "clarity"
Then("the Set should have size 1") assert(set.size === 1)
And("the Set should contain the added element") assert(set.contains("clarity"))
markup("This test finished with a **bold** statement!") } } }Although all of ScalaTest's built-in reporters will display the markup text in some form, the HTML reporter will format the markup information into HTML. Thus, the main purpose of
markup
is to add nicely formatted text to HTML reports. Here's what the aboveSetSpec
would look like in the HTML reporter:Notifiers and alerters
ScalaTest records text passed to
info
andmarkup
during tests, and sends the recorded text in therecordedEvents
field of test completion events likeTestSucceeded
andTestFailed
. This allows string reporters (like the standard out reporter) to showinfo
andmarkup
text after the test name in a color determined by the outcome of the test. For example, if the test fails, string reporters will show theinfo
andmarkup
text in red. If a test succeeds, string reporters will show theinfo
andmarkup
text in green. While this approach helps the readability of reports, it means that you can't useinfo
to get status updates from long running tests.To get immediate (i.e., non-recorded) notifications from tests, you can use
note
(aNotifier
) andalert
(anAlerter
). Here's an example showing the differences:package org.scalatest.examples.featurespec.note
import collection.mutable import org.scalatest._
class SetSpec extends FeatureSpec {
feature("An element can be added to an empty mutable Set") { scenario("When an element is added to an empty mutable Set") {
info("info is recorded") markup("markup is *also* recorded") note("notes are sent immediately") alert("alerts are also sent immediately")
val set = mutable.Set.empty[String] set += "clarity" assert(set.size === 1) assert(set.contains("clarity")) } } }Because
note
andalert
information is sent immediately, it will appear before the test name in string reporters, and its color will be unrelated to the ultimate outcome of the test:note
text will always appear in green,alert
text will always appear in yellow. Here's an example:scala> org.scalatest.run(new SetSpec) SetSpec: Feature: An element can be added to an empty mutable Set + notes are sent immediately + alerts are also sent immediately Scenario: When an element is added to an empty mutable Set info is recorded + markup is *also* recorded
Another example is slowpoke notifications. If you find a test is taking a long time to complete, but you're not sure which test, you can enable slowpoke notifications. ScalaTest will use an
Alerter
to fire an event whenever a test has been running longer than a specified amount of time.In summary, use
info
andmarkup
for text that should form part of the specification output. Usenote
andalert
to send status notifications. (Because the HTML reporter is intended to produce a readable, printable specification,info
andmarkup
text will appear in the HTML report, butnote
andalert
text will not.)Pending tests
A pending test is one that has been given a name but is not yet implemented. The purpose of pending tests is to facilitate a style of testing in which documentation of behavior is sketched out before tests are written to verify that behavior (and often, before the behavior of the system being tested is itself implemented). Such sketches form a kind of specification of what tests and functionality to implement later.
To support this style of testing, a test can be given a name that specifies one bit of behavior required by the system being tested. The test can also include some code that sends more information about the behavior to the reporter when the tests run. At the end of the test, it can call method
pending
, which will cause it to complete abruptly withTestPendingException
.Because tests in ScalaTest can be designated as pending with
TestPendingException
, both the test name and any information sent to the reporter when running the test can appear in the report of a test run. (In other words, the code of a pending test is executed just like any other test.) However, because the test completes abruptly withTestPendingException
, the test will be reported as pending, to indicate the actual test, and possibly the functionality, has not yet been implemented. You can mark tests as pending in aFeatureSpec
like this:package org.scalatest.examples.featurespec.pending
import org.scalatest.FeatureSpec
class TVSet { private var on: Boolean = false def isOn: Boolean = on def pressPowerButton() { on = !on } }
class TVSetSpec extends FeatureSpec {
feature("TV power button") {
scenario("User presses power button when TV is off") (pending)
scenario("User presses power button when TV is on") { val tv = new TVSet tv.pressPowerButton() assert(tv.isOn) tv.pressPowerButton() assert(!tv.isOn) } } }(Note: "
(pending)
" is the body of the test. Thus the test contains just one statement, an invocation of thepending
method, which throwsTestPendingException
.) If you run this version ofTVSetSpec
with:scala> org.scalatest.run(new TVSetSpec)
It will run both tests, but report that
When empty should have size 0
is pending. You'll see:TVSetSpec: Feature: TV power button Scenario: User presses power button when TV is off (pending) Scenario: User presses power button when TV is on
One difference between an ignored test and a pending one is that an ignored test is intended to be used during a significant refactorings of the code under test, when tests break and you don't want to spend the time to fix all of them immediately. You can mark some of those broken tests as ignored temporarily, so that you can focus the red bar on just failing tests you actually want to fix immediately. Later you can go back and fix the ignored tests. In other words, by ignoring some failing tests temporarily, you can more easily notice failed tests that you actually want to fix. By contrast, a pending test is intended to be used before a test and/or the code under test is written. Pending indicates you've decided to write a test for a bit of behavior, but either you haven't written the test yet, or have only written part of it, or perhaps you've written the test but don't want to implement the behavior it tests until after you've implemented a different bit of behavior you realized you need first. Thus ignored tests are designed to facilitate refactoring of existing code whereas pending tests are designed to facilitate the creation of new code.
One other difference between ignored and pending tests is that ignored tests are implemented as a test tag that is excluded by default. Thus an ignored test is never executed. By contrast, a pending test is implemented as a test that throws
TestPendingException
(which is what calling thepending
method does). Thus the body of pending tests are executed up until they throwTestPendingException
. The reason for this difference is that it enables your unfinished test to sendInfoProvided
messages to the reporter before it completes abruptly withTestPendingException
, as shown in the previous example onInformer
s that used theGivenWhenThen
trait. For example, the following snippet in aFeatureSpec
:package org.scalatest.examples.featurespec.infopending
import org.scalatest._
class TVSet { private var on: Boolean = false
def isOn: Boolean = on
def pressPowerButton() { on = !on } }
class TVSetSpec extends FeatureSpec with GivenWhenThen {
info("As a TV set owner") info("I want to be able to turn the TV on and off") info("So I can watch TV when I want") info("And save energy when I'm not watching TV")
feature("TV power button") { scenario("User presses power button when TV is off") { Given("a TV that is switched off") When("the power button is pressed") Then("the TV should switch on") pending }
scenario("User presses power button when TV is on") { Given("a TV that is switched on") When("the power button is pressed") Then("the TV should switch off") pending } } }Would yield the following output when run in the interpreter:
scala> org.scalatest.run(new TVSetSpec) TVSetSpec: As a TV set owner I want to be able to turn the TV on and off So I can watch TV when I want And save energy when I'm not watching TV Feature: TV power button Scenario: User presses power button when TV is off (pending) Given a TV that is switched off When the power button is pressed Then the TV should switch on Scenario: User presses power button when TV is on (pending) Given a TV that is switched on When the power button is pressed Then the TV should switch off
Tagging tests
A
FeatureSpec
's tests may be classified into groups by tagging them with string names. As with any suite, when executing aFeatureSpec
, groups of tests can optionally be included and/or excluded. To tag aFeatureSpec
's tests, you pass objects that extend classorg.scalatest.Tag
to methods that register tests. ClassTag
takes one parameter, a string name. If you have created tag annotation interfaces as described in theTag
documentation, then you will probably want to use tag names on your test functions that match. To do so, simply pass the fully qualified names of the tag interfaces to theTag
constructor. For example, if you've defined a tag annotation interface with fully qualified name,com.mycompany.tags.DbTest
, then you could create a matching tag forFeatureSpec
s like this:package org.scalatest.examples.featurespec.tagging
import org.scalatest.Tag
object DbTest extends Tag("com.mycompany.tags.DbTest")Given these definitions, you could place
FeatureSpec
tests into groups with tags like this:import org.scalatest.FeatureSpec import org.scalatest.tagobjects.Slow
class TVSet { private var on: