From 5e4831de7737dddf117c0cb8e5e4de0cfd1b309b Mon Sep 17 00:00:00 2001
From: Matthias Berndt Sharing fixtures
The extract method refactor helps you create fresh instances of mutable fixture objects in each test
- that needs them, but doesn't help you clean them up when you're done.
+ that needs them. The
ResourceManager
trait can be used to perform any necessary cleanup.
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 +
If you need to create the same mutable fixture objects in multiple tests, 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:
@@ -187,10 +187,63 @@If you need to configure fixture objects differently in different tests, you can pass configuration into the get-fixture method. For example, if you could pass in an initial value for a mutable fixture object as a parameter to the get-fixture method.
+If one or multiple of your fixture objects require cleanup, you can use the ResourceManagerFixture
trait. It gives you access to a
+org.scalactic.Using.Manager
object that can be used to register cleanup functions which will be run in reverse order after the test completes.
+This class works exactly like the scala.util.Using.Manager
class available in Scala 2.13 and later. The recommended way to use it is to pass it
+as a parameter to any methods that acquire resources that require cleanup. As soon as the resource has been allocated, it should be passed to the
+Using.Manager
to register the necessary cleanup.
+
+Here's an example:
+
+
+package org.scalatest.examples.flatspec.getfixture +import org.scalatest.flatspec.FixtureAnyFlatSpec +import org.scalatest.fixture.ResourceManagerFixture +import org.scalactic.Using + +class ExampleSpec extends FixtureAnyFlatSpec with ResourceManagerFixture { + def fixture(use: Using.Manager) = + use(getClass().getResourceAsStream("/some/resource.txt")) + + "Tests" should "clean up after themselves" in { use => + val stream = fixture(use) + + // use stream here + // stream will be closed after test is done + } +} ++ + +
+For objects that implement the java.lang.AutoCloseable
interface, Using.Manager
works out of the box. Most objects that require
+cleanup, like the stream in the example above, already do this. If yours doesn't, you have several options. You can provide a given
instance
+of the org.scalactic.Using.Releasable[A]
trait (on Scala 2.13 and later, this is merely an alias for the standard library's
+scala.util.Using.Releasable
) trait. The best way to do this is to place it in the relant type's companion object, ensuring that no imports
+are required for the compiler to find it.
+
+If a given Using.Relesable
is not desired, you can register cleanup calls using lambda syntax like so:
+
+import java.nio.file.Files +… + def tempFile(use: Using.Manager) = { + val path = Files.createTempFile("temp-file", null) + use[AutoCloseable] { () => + Files.delete(path) + } + path + } +… ++This makes use of the fact that lambda expressions in Scala can be used to implement any interface with a single abstract method – +like
AutoCloseable
.
+
+
An alternate technique that is especially useful when different tests need different combinations of fixture objects is to define the fixture objects as instance variables -of fixture-context objects whose instantiation forms the body of tests. Like get-fixture methods, fixture-context objects are only +of fixture-context objects whose instantiation forms the body of tests. Fixture-context objects are only appropriate if you don't need to clean up the fixtures after using them.
To use this technique, you define instance variables intialized with fixture objects in traits and/or classes, then in each test instantiate an object that @@ -316,7 +369,7 @@
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 you need to both pass a fixture object into a test and perform cleanup at the end of the test, you can 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.
@@ -501,6 +554,34 @@BeforeAndAfterEach
instead, as shown later in the next section,
composing fixtures by stacking traits.
+ResourceManager
Sometimes it is desirable to share fixtures across tests. This is often the case for fixtures that are expensive to create,
+ like a container started using the testcontainers-scala
library. To make sure these are properly cleaned up after the
+ suite has finished, ScalaTest includes the ResourceManager
trait. Similar to the ResourceManagerFixture
+ trait discussed above, it gives you access to a Using.Manager
instance. The differences are that it isn't passed
+ as a fixture parameter but via the suiteScoped
method, and that it won't perform cleanup after every test but after
+ all tests in the suite have finished.
+ The intended usage is with lazy val
s at the suite level. Here's an example
+
+
+ class MySuite extends AnyFunSuite with ResourceManager { + lazy val sharedClient = suiteScoped(new ExpensiveClient) + + test("something") { + // Use `sharedClient` inside the test. + // `sharedClient` will be closed after all tests have finished executing + } + } ++ + Note that
suiteScoped
must not be called during initialization of the suite, which is why all suite-scoped resources should be declared
+ using lazy
in order to defer initialization until the tests start running.
+ An added benefit is that this might avoid initialization of such resources entirely, for example when you're only running a subset of the tests in this
+ suite that don't require that particular fixture. You can even place such a fixture in a shared base class for your tests and it will be instantiated
+ if and only if required by the tests implemented in your suite.
+
+
In larger projects, teams often end up with several different fixtures that test classes need in different combinations,