I would like to add my own use-case for DelayedInit
, perhaps contributing to the argument that it used in more cases than just a few niche ones. In my use case I’m trying to add an ability to chain test cases written with ScalaTest. I’ll let the code explain it:
import com.typesafe.scalalogging.StrictLogging
import org.scalatest._
import scala.collection.mutable
/**
* A mixin (fun) suite which introduces the concept of chained tests; a chained test will be first executed like any
* other test in the suite, but after all the "normal" executions are done, the chained test will be executed in pairs
* with each of the other chained tests in the suite (including itself).
*
* For instance, if the suite has 2 "normal" tests (1, 2) and 3 chained tests (3, 4, 5), then first all the tests will
* be executed normally (1, 2, 3, 4, 5). Afterwards, each pair possible to construct from the chained tests will be
* executed as an individual test; a test of 3 and then 3, a test of 3 and then 4, 3 and then 5, 4 and then 3, etc
* (total of 3 * 3 = 9 additional tests).
*
* This is especially useful when testing units which are stateful by their nature, since it is vital to assert that
* the unit is capable of executing separate use-cases in succession without breaking its inner state.
*/
trait ChainedTestsSuite extends DelayedInit with StrictLogging {
self: FunSuiteLike =>
/* --- Data Members --- */
private val tests = mutable.Buffer.empty[ChainedTest]
/* --- Methods --- */
/* --- Public Methods --- */
override def delayedInit(suiteBody: => Unit): Unit = {
suiteBody
for (testA <- tests; testB <- tests) {
test(s"Chain test: ${testA.order} and then ${testB.order}") {
logger.info(s"Executing first test: ${testA.name}")
testA.testFun()
betweenChainedTests()
logger.info(s"Executing second test: ${testB.name}")
testB.testFun()
}
}
}
/**
* Override this in order to execute code between two chained tests.
*/
def betweenChainedTests(): Unit = {}
/* --- Protected Methods --- */
/**
* Registers a test as a chained test.
*
* @see [[FunSuiteLike.test]]
*/
protected def chainTest(testName: String, testTags: Tag*)(testFun: => Unit): Unit = {
val order = tests.size + 1
val chainedName = s"($order) $testName"
test(chainedName, testTags: _*)(testFun)
tests += ChainedTest(chainedName, order, () => testFun)
}
}
object ChainedTestsSuite {
/* --- Inner Classes --- */
private case class ChainedTest(name: String, order: Int, testFun: () => Unit)
}
I’m guessing this is an ability that could be added to ScalaTest without the need to use DelayedInit
, but for someone who wants to “act quickly” this is sometimes a viable feature of the language.