Implementation trait for class fixture.FunSpec
, which is
a sister class to org.scalatest.FunSpec
that can pass a
fixture object into its tests.
A sister class to org.scalatest.FunSpec
that can pass a fixture object into its tests.
A sister class to org.scalatest.FunSpec
that can pass a fixture object into its tests.
Recommended Usage:
Use class fixture.FunSpec in situations for which FunSpec
would be a good choice, when all or most tests need the same fixture objects
that must be cleaned up afterwards. Note: fixture.FunSpec is intended for use in special situations, with class FunSpec used for general needs. For
more insight into where fixture.FunSpec fits in the big picture, see the withFixture(OneArgTest) subsection of the Shared fixtures section in the documentation for class FunSpec .
|
Class fixture.FunSpec
behaves similarly to class org.scalatest.FunSpec
, except that tests may have a
fixture parameter. The type of the
fixture parameter is defined by the abstract FixtureParam
type, which is a member of this class.
This class also contains an abstract withFixture
method. This withFixture
method
takes a OneArgTest
, which is a nested trait defined as a member of this class.
OneArgTest
has an apply
method that takes a FixtureParam
.
This apply
method is responsible for running a test.
This class's runTest
method delegates the actual running of each test to withFixture(OneArgTest)
, passing
in the test code to run via the OneArgTest
argument. The withFixture(OneArgTest)
method (abstract in this class) is responsible
for creating the fixture argument and passing it to the test function.
Subclasses of this class must, therefore, do three things differently from a plain old org.scalatest.FunSpec
:
FixtureParam
withFixture(OneArgTest)
methodIf the fixture you want to pass into your tests consists of multiple objects, you will need to combine them into one object to use this class. One good approach to passing multiple fixture objects is to encapsulate them in a case class. Here's an example:
case class FixtureParam(file: File, writer: FileWriter)
To enable the stacking of traits that define withFixture(NoArgTest)
, it is a good idea to let
withFixture(NoArgTest)
invoke the test function instead of invoking the test
function directly. To do so, you'll need to convert the OneArgTest
to a NoArgTest
. You can do that by passing
the fixture object to the toNoArgTest
method of OneArgTest
. In other words, instead of
writing “test(theFixture)
”, you'd delegate responsibility for
invoking the test function to the withFixture(NoArgTest)
method of the same instance by writing:
withFixture(test.toNoArgTest(theFixture))
Here's a complete example:
package org.scalatest.examples.funspec.oneargtest import org.scalatest.fixture import java.io._ class ExampleSpec extends fixture.FunSpec { case class FixtureParam(file: File, writer: FileWriter) def withFixture(test: OneArgTest) = { // create the fixture val file = File.createTempFile("hello", "world") val writer = new FileWriter(file) val theFixture = FixtureParam(file, writer) try { writer.write("ScalaTest is ") // set up the fixture withFixture(test.toNoArgTest(theFixture)) // "loan" the fixture to the test } finally writer.close() // clean up the fixture } describe("Testing") { it("should be easy") { f => f.writer.write("easy!") f.writer.flush() assert(f.file.length === 18) } it("should be fun") { f => f.writer.write("fun!") f.writer.flush() assert(f.file.length === 17) } } }
If a test fails, the OneArgTest
function will result in a Failed wrapping the exception describing the failure.
To ensure clean up happens even if a test fails, you should invoke the test function from inside a try
block and do the cleanup in a
finally
clause, as shown in the previous example.
If multiple test classes need the same fixture, you can define the FixtureParam
and withFixture(OneArgTest)
implementations
in a trait, then mix that trait into the test classes that need it. For example, if your application requires a database and your integration tests
use that database, you will likely have many test classes that need a database fixture. You can create a "database fixture" trait that creates a
database with a unique name, passes the connector into the test, then removes the database once the test completes. This is shown in the following example:
package org.scalatest.examples.fixture.funspec.sharing import java.util.concurrent.ConcurrentHashMap import org.scalatest.fixture import DbServer._ import java.util.UUID.randomUUID object DbServer { // Simulating a database server type Db = StringBuffer private val databases = new ConcurrentHashMap[String, Db] def createDb(name: String): Db = { val db = new StringBuffer databases.put(name, db) db } def removeDb(name: String) { databases.remove(name) } } trait DbFixture { this: fixture.Suite => type FixtureParam = Db // Allow clients to populate the database after // it is created def populateDb(db: Db) {} def withFixture(test: OneArgTest) = { val dbName = randomUUID.toString val db = createDb(dbName) // create the fixture try { populateDb(db) // setup the fixture withFixture(test.toNoArgTest(db)) // "loan" the fixture to the test } finally removeDb(dbName) // clean up the fixture } } class ExampleSpec extends fixture.FunSpec with DbFixture { override def populateDb(db: Db) { // setup the fixture db.append("ScalaTest is ") } describe("Testing") { it("should be easy") { db => db.append("easy!") assert(db.toString === "ScalaTest is easy!") } it("should be fun") { db => db.append("fun!") assert(db.toString === "ScalaTest is fun!") } } // This test doesn't need a Db describe("Test code") { it("should be clear") { () => val buf = new StringBuffer buf.append("ScalaTest code is ") buf.append("clear!") assert(buf.toString === "ScalaTest code is clear!") } } }
Often when you create fixtures in a trait like DbFixture
, you'll still need to enable individual test classes
to "setup" a newly created fixture before it gets passed into the tests. A good way to accomplish this is to pass the newly
created fixture into a setup method, like populateDb
in the previous example, before passing it to the test
function. Classes that need to perform such setup can override the method, as does ExampleSpec
.
If a test doesn't need the fixture, you can indicate that by providing a no-arg instead of a one-arg function, as is done in the
third test in the previous example, “Test code should be clear
”. In other words, instead of starting your function literal
with something like “db =>
”, you'd start it with “() =>
”. For such tests, runTest
will not invoke withFixture(OneArgTest)
. It will instead directly invoke withFixture(NoArgTest)
.
Both examples shown above demonstrate the technique of giving each test its own "fixture sandbox" to play in. When your fixtures
involve external side-effects, like creating files or databases, it is a good idea to give each file or database a unique name as is
done in these examples. This keeps tests completely isolated, allowing you to run them in parallel if desired. You could mix
ParallelTestExecution
into either of these ExampleSpec
classes, and the tests would run in parallel just fine.
Implementation trait for class fixture.FunSpec
, which is
a sister class to org.scalatest.FunSpec
that can pass a
fixture object into its tests.
Implementation trait for class fixture.FunSpec
, which is
a sister class to org.scalatest.FunSpec
that can pass a
fixture object into its tests.
fixture.FunSpec
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 of fixture.FunSpec
into some other
class, you can use this trait instead, because class
fixture.FunSpec
does nothing more than extend this trait and add a nice toString
implementation.
See the documentation of the class for a detailed
overview of fixture.FunSpec
.
Implementation trait for class
fixture.FunSpec
, which is a sister class toorg.scalatest.FunSpec
that can pass a fixture object into its tests.fixture.FunSpec
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 offixture.FunSpec
into some other class, you can use this trait instead, because classfixture.FunSpec
does nothing more than extend this trait and add a nicetoString
implementation.See the documentation of the class for a detailed overview of
fixture.FunSpec
.