If the intent is to be able to access the test name from anywhere, as was suggested by @kajmanus in previous comments, a ThreadLocal fits the bill nicely.
You could define a case class to store the info you require for the current test context. e.g.,
case class TestContext(name: Option[String] = None)
object TestContext {
val currentTest: ThreadLocal[TestContext] =
ThreadLocal.withInitial(() => TestContext())
}
Then define a trait your various specs will extend. e.g.,
trait BaseFunSpec
extends AnyFunSpec
...
{
override protected def withFixture(test: NoArgTest): Outcome = {
try {
TestContext.currentTest.set(TestContext(name = Some(test.name)))
super.withFixture(test)
} finally {
TestContext.currentTest.remove()
}
}
}
Finally, you can access the current test context you've set for the current thread (which in this example is purely the test name) from anywhere within the current thread as needed. e.g.,
def cachedResults(bytes: Array[Byte], fileType: String): Unit = {
TestContext.currentTest.get().name match {
case Some(testname) =>
import scala.util.Using
val file = new File("target", s"${testname}.${fileType}")
Using(new BufferedOutputStream(new FileOutputStream(file))) { os =>
os.write(bytes)
}
case None => throw new IllegalStateException("Unknown test context")
}
}
This will work whether you're running tests in parallel or not, assuming you're not processing things asynchronously (i.e., in another thread).
A cleaner usage of this is to create purposed actors. e.g.,
case class TestContext(name: Option[String] = None)
object TestContext {
val currentTest: ThreadLocal[TestContext] = ThreadLocal.withInitial(() => TestContext())
class TestNamer {
def currentName: String = currentTest.get().name match {
case Some(testname) => testname
case None => throw new IllegalStateException("No test context available")
}
}
class TestContextWriter(testNamer: TestNamer = new TestNamer()) {
def cachedBytes(bytes: Array[Byte], extension: String): Array[Byte] = {
import java.io.{BufferedOutputStream, File, FileOutputStream}
import scala.util.Using
val file = new File("target", s"${testNamer.currentName}.${extension}")
Using(new BufferedOutputStream(new FileOutputStream(file))) { outstream =>
outstream.write(bytes)
}
bytes
}
}
}
And inject as needed:
trait BaseFunSpec {
val testContextWriter = new TestContextWriter()
def fetchRawResults(...): Array[Byte] = {
...
testContextWriter.cachedBytes(bytes, "pdf")
}
}
def currentTestName: String
approach actually, that you can access from anywhere without passing any parameters along (and modifying function signatures just for debugging purposes). From my point of view, the test name is not really part of the test — it's only debugging info. But when it appears as a parameter to the test (td =>
) then it seems to be part of the test. – Oblige