Repository: 7mind/izumi
Pull request #1357 (2021-01-11 10:52:30)

Make all tests in `distage-testkit` docs run, improve examples, rewrite Out-of-the-box-typeclass instances chapter, Make `ZIO Has Bindings` example more understandable


Patch:
diff --git a/README.md b/README.md
index d57de8cd8..8ee83fa30 100644
--- a/README.md
+++ b/README.md
@@ -127,7 +127,7 @@ Are you using Izumi? Please consider opening a pull request to list your organiz
 
 Projects powered by Izumi
 ==========================
-- [d4s](https://github.com/PlayQ/d4s) - "Dynamo DB Database done Scala way". A library that allows accessing the DynamoDB in a purely functional way.
+- [d4s](https://github.com/PlayQ/d4s) - "Dynamo DB Database done Scala way". A library that allows accessing the DynamoDB in a purely-functional way.
 
 Credits
 =======
diff --git a/build.sbt b/build.sbt
index 507e57c38..5a0e63a1e 100644
--- a/build.sbt
+++ b/build.sbt
@@ -2817,6 +2817,8 @@ lazy val `microsite` = project.in(file("doc/microsite"))
       "dev.zio" %% "zio-interop-cats" % V.zio_interop_cats excludeAll("dev.zio" %% "izumi-reflect"),
       "org.tpolecat" %% "doobie-core" % V.doobie,
       "org.tpolecat" %% "doobie-postgres" % V.doobie,
+      "io.monix" %% "monix" % V.monix,
+      "io.monix" %% "monix-bio" % V.monix_bio,
       "dev.zio" %% "izumi-reflect" % V.izumi_reflect
     )
   )
diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/support/MonixBIOSupportModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/support/MonixBIOSupportModule.scala
index b5649df98..14d59d1f6 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/modules/support/MonixBIOSupportModule.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/modules/support/MonixBIOSupportModule.scala
@@ -18,12 +18,13 @@ object MonixBIOSupportModule extends MonixBIOSupportModule
   *  - Adds [[izumi.functional.bio]] typeclass instances for `monix-bio`
   *  - Adds `cats-effect` typeclass instances for `monix-bio`
   *
-  * @note Will also add the following components:
+  * Will also add the following components:
   *   - [[monix.execution.Scheduler Scheduler]] by using [[monix.execution.Scheduler.global]]
   *   - [[monix.execution.Scheduler Scheduler @Id("io")]] by using [[monix.execution.Scheduler.io]]
   *   - [[monix.bio.IO.Options]] by using [[monix.bio.IO.defaultOptions]]
   *   - [[cats.effect.Blocker]] by using `Scheduler @Id("io")`
   *
+  * Added into scope by [[izumi.distage.modules.DefaultModule]].
   * Bindings to the same keys in your own [[izumi.distage.model.definition.ModuleDef]] or plugins will override these defaults.
   */
 trait MonixBIOSupportModule extends ModuleDef with MonixBIOPlatformDependentSupportModule {
diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/support/MonixSupportModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/support/MonixSupportModule.scala
index 71ed89cff..a07b85e87 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/modules/support/MonixSupportModule.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/modules/support/MonixSupportModule.scala
@@ -15,12 +15,13 @@ object MonixSupportModule extends MonixSupportModule
   *  - Adds [[izumi.distage.model.effect.QuasiIO]] instances to support using `monix` in `Injector`, `distage-framework` & `distage-testkit-scalatest`
   *  - Adds `cats-effect` typeclass instances for `monix`
   *
-  * @note Will also add the following components:
+  * Will also add the following components:
   *   - [[monix.execution.Scheduler Scheduler]] by using [[monix.execution.Scheduler.global]]
   *   - [[monix.execution.Scheduler Scheduler @Id("io")]] by using [[monix.execution.Scheduler.io]]
   *   - [[monix.eval.Task.Options]] by using [[monix.eval.Task.defaultOptions]]
   *   - [[cats.effect.Blocker]] by using `Scheduler @Id("io")`
   *
+  * Added into scope by [[izumi.distage.modules.DefaultModule]].
   * Bindings to the same keys in your own [[izumi.distage.model.definition.ModuleDef]] or plugins will override these defaults.
   */
 trait MonixSupportModule extends ModuleDef with MonixPlatformDependentSupportModule {
diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/support/ZIOSupportModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/support/ZIOSupportModule.scala
index 9b7c5f978..b626b9b9d 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/modules/support/ZIOSupportModule.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/modules/support/ZIOSupportModule.scala
@@ -13,12 +13,14 @@ object ZIOSupportModule extends ZIOSupportModule
   *  - Adds [[izumi.distage.model.effect.QuasiIO]] instances to support using ZIO in `Injector`, `distage-framework` & `distage-testkit-scalatest`
   *  - Adds [[izumi.functional.bio]] typeclass instances for ZIO
   *
-  * @note Will also add the following components:
+  * Will also add the following components:
   *   - `ThreadPoolExecutor @Id("zio.cpu")` for CPU-bound tasks (will be used for all tasks by default by [[zio.Runtime]])
   *   - `ThreadPoolExecutor @Id("zio.io")` and blocking IO tasks (tasks can be scheduled to it via [[izumi.functional.bio.BlockingIO]] or [[zio.blocking.blocking]])
   *   - `ExecutionContext @Id("zio.cpu")` & `ExecutionContext @Id("zio.io")` respectively
   *   - [[zio.internal.tracing.TracingConfig]] will be set to [[zio.internal.tracing.TracingConfig.enabled]] by default
+  *   - Standard ZIO services: [[zio.console.Console]], [[zio.clock.Clock]], [[zio.system.System]], [[zio.random.Random]] and corresponding `.Service` types
   *
+  * Added into scope by [[izumi.distage.modules.DefaultModule]].
   * Bindings to the same keys in your own [[izumi.distage.model.definition.ModuleDef]] or plugins will override these defaults.
   */
 trait ZIOSupportModule extends ModuleDef with ZIOPlatformDependentSupportModule {
diff --git a/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/ZIOCatsEffectInstancesModule.scala b/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/ZIOCatsEffectInstancesModule.scala
index 5c3eb04ad..8194bcc14 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/ZIOCatsEffectInstancesModule.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/modules/typeclass/ZIOCatsEffectInstancesModule.scala
@@ -17,7 +17,7 @@ object ZIOCatsEffectInstancesModule extends ZIOCatsEffectInstancesModule
   *
   * Depends on `zio.Runtime[Any]` and `ThreadPoolExecutor @Id("zio.io")` (both can be found in [[izumi.distage.modules.support.ZIOSupportModule]])
   *
-  * @note Will also add the following components:
+  * Will also add the following components:
   *   - [[cats.effect.Blocker]] by using `ThreadPoolExecutor @Id("zio.io")`
   */
 trait ZIOCatsEffectInstancesModule extends ModuleDef {
diff --git a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/ForcedRecompilationToken.scala b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/ForcedRecompilationToken.scala
index 478a8a404..1a07c9549 100644
--- a/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/ForcedRecompilationToken.scala
+++ b/distage/distage-extension-plugins/src/main/scala/izumi/distage/plugins/ForcedRecompilationToken.scala
@@ -80,7 +80,10 @@ object ForcedRecompilationToken {
 //      UniqueRecompilationTokenMacro.synchronized {
 //        if (cachedTypedTree eq null) {
 //          val uuidStrConstantType = internal.constantType(Constant(compilerLaunchId))
-//          val tree = c.typecheck(q"null : _root_.izumi.distage.plugins.ForcedRecompilationToken[$uuidStrConstantType]")
+////          val tree = c.typecheck(q"null : _root_.izumi.distage.plugins.ForcedRecompilationToken[$uuidStrConstantType]")
+//          val tree = q"null"
+//          import c.internal.reificationSupport._
+//          setType(tree, appliedType(typeOf[ForcedRecompilationToken[_]].typeConstructor, uuidStrConstantType))
 //          cachedTypedTree = tree
 //        }
 //        cachedTypedTree.asInstanceOf[c.Tree]
diff --git a/doc/microsite/src/main/tut/distage/basics.md b/doc/microsite/src/main/tut/distage/basics.md
index 633180f26..df87ad005 100644
--- a/doc/microsite/src/main/tut/distage/basics.md
+++ b/doc/microsite/src/main/tut/distage/basics.md
@@ -413,7 +413,10 @@ Will produce the following output:
 ```scala mdoc:to-string
 import distage.DIKey
 
-val objectGraphResource = Injector[IO]().produce(module, Roots(root = DIKey[MyApp]))
+val objectGraphResource = {
+  Injector[IO]()
+    .produce(module, Roots.target[MyApp])
+}
 
 objectGraphResource
   .use(_.get[MyApp].run)
@@ -525,6 +528,50 @@ The following helpers ease defining `Lifecycle` sub-classes using traditional in
 - @scaladoc[Lifecycle.SelfNoClose](izumi.distage.model.definition.Lifecycle$$SelfNoClose)
 - @scaladoc[Lifecycle.NoClose](izumi.distage.model.definition.Lifecycle$$NoClose)
 
+## Out-of-the-box typeclass instances
+
+Typeclass instances for popular typeclass hierarchies are included by default for the effect type in which `distage` is running.
+
+Whenever your effect type implements @ref[BIO](../bio/00_bio.md) or [cats-effect](https://typelevel.org/cats-effect/) typeclasses, their instances will be summonable without adding them into modules.
+This applies for `ZIO`, `cats.effect.IO`, `monix`, `monix-bio` and any other effect type with relevant typeclass instances in implicit scope.
+
+- For `ZIO`, `monix-bio` and any other implementors of @ref[BIO](../bio/00_bio.md) typeclasses, `BIO` hierarchy instances will be included.
+- For `ZIO`, `cats-effect` instances will be included only if ZIO [`interop-cats`](https://github.com/zio/interop-cats/) library is on the classpath.
+
+Example usage:
+
+```scala mdoc:reset:to-string
+import cats.effect.{IO, Sync}
+import distage.{Activation, DefaultModule, Injector, Module, TagK}
+import izumi.distage.model.effect.QuasiIO
+
+def polymorphicHelloWorld[F[_]: TagK: QuasiIO: DefaultModule]: F[Unit] = {
+  Injector[F]().produceRun(
+    Module.empty, // we do not define _any_ components
+    Activation.empty,
+  ) {
+      (F: Sync[F]) => // cats.effect.Sync[F] is available anyway
+        F.delay(println("Hello world!"))
+  }
+}
+
+val catsEffectHello = polymorphicHelloWorld[cats.effect.IO]
+
+val monixHello = polymorphicHelloWorld[monix.eval.Task]
+
+val zioHello = polymorphicHelloWorld[zio.IO[Throwable, ?]]
+
+val monixBioHello = polymorphicHelloWorld[monix.bio.IO[Throwable, ?]]
+```
+
+See @scaladoc[`DefaultModule`](izumi.distage.modules.DefaultModule) implicit for implementation details. For details on
+what exact components are available for each effect type, see
+@scaladoc[ZIOSupportModule](izumi.distage.modules.support.ZIOSupportModule),
+@scaladoc[CatsIOSupportModule](izumi.distage.modules.support.CatsIOSupportModule),
+@scaladoc[MonixSupportModule](izumi.distage.modules.support.MonixSupportModule),
+@scaladoc[MonixBIOSupportModule](izumi.distage.modules.support.MonixBIOSupportModule),
+@scaladoc[ZIOCatsEffectInstancesModule](izumi.distage.modules.typeclass.ZIOCatsEffectInstancesModule), respectively.
+
 ## Set Bindings
 
 Set bindings are useful for implementing listeners, plugins, hooks, http routes, healthchecks, migrations, etc.
@@ -747,48 +794,58 @@ You need to specify your effect type when constructing `Injector`, as in `Inject
 
 You can inject into ZIO Environment using `make[_].fromHas` syntax for `ZLayer`, `ZManaged`, `ZIO` or any `F[_, _, _]: Local3`:
 
-```scala mdoc:reset:invisible
-class Dep1
-class Dep2
-class Arg1
-class Arg2
+```scala mdoc:to-string
+import zio._
+import zio.console.{Console, putStrLn}
+import distage.ModuleDef
+
+class Dependency
 
-class X
-object X extends X {
-  def apply(a: Arg1, b: Arg2, d: Dep1): X = X
+class X(dependency: Dependency)
+
+def makeX: URIO[Console with Has[Dependency], X] = {
+  for {
+    dep <- ZIO.service[Dependency]
+    _   <- putStrLn(s"Obtained environment dependency = $dep")
+  } yield new X(dep)
 }
-```
 
-```scala mdoc:to-string
-import zio._
-import distage._
+def makeXManaged: URManaged[Console with Has[Dependency], X] = makeX.toManaged_
 
-def zioEnvCtor: URIO[Has[Dep1] with Has[Dep2], X] = ZIO.succeed(X)
-def zmanagedEnvCtor: URManaged[Has[Dep1] with Has[Dep2], X] = ZManaged.succeed(X)
-def zlayerEnvCtor: URLayer[Has[Dep1] with Has[Dep2], Has[X]] = ZLayer.succeed(X)
+def makeXLayer: URLayer[Console with Has[Dependency], Has[X]] = makeX.toLayer
 
 def module1 = new ModuleDef {
-  make[X].fromHas(zioEnvCtor)
+  make[Dependency]
+
+  make[X].fromHas(makeX)
   // or
-  make[X].fromHas(zmanagedEnvCtor)
+  make[X].fromHas(makeXManaged)
   // or
-  make[X].fromHas(zlayerEnvCtor)
+  make[X].fromHas(makeXLayer)
 }
 ```
 
 You can also mix environment and parameter dependencies at the same time in one constructor:
 
 ```scala mdoc:to-string
-def zioArgEnvCtor(a: Arg1, b: Arg2): URLayer[Has[Dep1], Has[X]] = ZLayer.fromService(dep1 => X(a, b, dep1))
+def zioArgEnvCtor(
+  dependency: Dependency
+): URLayer[Console, Has[X]] = {
+  ZLayer.succeed(dependency) ++
+  ZLayer.identity[Console] >>>
+  makeX.toLayer
+}
 
 def module2 = new ModuleDef {
+  make[Dependency]
+
   make[X].fromHas(zioArgEnvCtor _)
 }
 ```
 
 `zio.Has` values are derived at compile-time by @scaladoc[HasConstructor](izumi.distage.constructors.HasConstructor) macro and can be summoned at need.
 
-Example:
+Another example:
 
 ```scala mdoc:reset:to-string
 import distage.{ModuleDef, Injector}
@@ -841,7 +898,6 @@ val turboFunctionalHelloWorld: URIO[Has[Hello] with Has[World] with Has[Console.
 def module = new ModuleDef {
   make[Hello].fromHas(makeHello)
   make[World].fromHas(makeWorld)
-  make[Console.Service].fromHas(Console.live)
   make[Unit].fromHas(turboFunctionalHelloWorld)
 }
 
@@ -1158,8 +1214,8 @@ Advantages of `distage` as a driver for TF compared to implicits:
 - extremely easy & scalable @ref[test](distage-testkit.md#testkit) context setup due to the above
 - multiple different implementations for a type using disambiguation by `@Id`
 
-For example, let's take [freestyle's tagless example](http://frees.io/docs/core/handlers/#tagless-interpretation)
-and make it safer and more flexible by replacing dependencies on global `import`ed implementations from with explicit modules.
+For example, let's take [`freestyle`'s tagless example](http://frees.io/docs/core/handlers/#tagless-interpretation)
+and make it better by replacing dependencies on global `import`ed implementations with explicit modules.
 
 First, the program we want to write:
 
@@ -1237,16 +1293,6 @@ val objectsLifecycle = Injector[IO]().produce(SyncProgram[IO], Roots.Everything)
 objectsLifecycle.use(_.get[TaglessProgram[IO]].program).unsafeRunSync()
 ```
 
-### Out-of-the-box typeclass instances
-
-Note: we did not have to include either `Monad[IO]` or `Sync[IO]` instances for our program to run,
-despite the fact that `SyncInteraction` depends on `Sync[F]`.
-
-This is because `distage` includes all the relevant typeclass instances for the provided effect type by default, using implicits.
-In general whenever `cats-effect` is on the classpath and your effect type has `cats-effect` instances, they will be included, this is valid for `ZIO`, `cats.effect.IO`, `monix`, `monix-bio` and any other type with cats-effect instances.
-For ZIO, `monix-bio` and any other implementors of @ref[BIO](../bio/00_bio.md), instances of bifunctor @ref[BIO](../bio/00_bio.md) effect hierarchy will also be included.
-See @scaladoc[`DefaultModule`](izumi.distage.modules.DefaultModule) for more details.
-
 ### Effect-type polymorphism
 
 The program module is polymorphic over effect type. It can be instantiated by a different effect:
diff --git a/doc/microsite/src/main/tut/izumi.md b/doc/microsite/src/main/tut/izumi.md
index dbe903af0..f103f1689 100644
--- a/doc/microsite/src/main/tut/izumi.md
+++ b/doc/microsite/src/main/tut/izumi.md
@@ -91,7 +91,7 @@ Videos:
 * [DevInsideYou — Tagless Final with BIO](https://www.youtube.com/watch?v=ZdGK1uedAE0&t=580s)
 
 Slides:
-
+ 
 * [Izumi 1.0: Your Next Scala Stack](https://www.slideshare.net/7mind/izumi-10-your-next-scala-stack)
 * [Scala, Functional Programming and Team Productivity](https://www.slideshare.net/7mind/scala-functional-programming-and-team-productivity)
 * [Hyper-pragmatic Pure FP Testing with distage-testkit](https://www.slideshare.net/7mind/hyperpragmatic-pure-fp-testing-with-distagetestkit)
diff --git a/project/Deps.sc b/project/Deps.sc
index 8a41a3528..4b1ba1596 100644
--- a/project/Deps.sc
+++ b/project/Deps.sc
@@ -569,7 +569,7 @@ object Izumi {
     artifacts = Seq(
       Artifact(
         name = Projects.docs.microsite,
-        libs = (cats_all ++ zio_all ++ doobie).map(_ in Scope.Compile.all) ++ Seq(izumi_reflect in Scope.Compile.all),
+        libs = (cats_all ++ zio_all ++ doobie ++ Seq(monix, monix_bio)).map(_ in Scope.Compile.all) ++ Seq(izumi_reflect in Scope.Compile.all),
         depends = all.flatMap(_.artifacts).map(_.name in Scope.Compile.all).distinct,
         settings = Seq(
           "scalacOptions" -= "-Wconf:any:error",


Test patch:
diff --git a/doc/microsite/src/main/tut/distage/distage-testkit.md b/doc/microsite/src/main/tut/distage/distage-testkit.md
index 042e5dbc2..395966436 100644
--- a/doc/microsite/src/main/tut/distage/distage-testkit.md
+++ b/doc/microsite/src/main/tut/distage/distage-testkit.md
@@ -30,12 +30,9 @@ Usage of `distage-testkit` generally follows these steps:
    dependencies:
     - No effect type / `Identity` -
       @scaladoc[`in`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$LowPriorityIdentityOverloads)
-    -
-    @scaladoc[`in` for `F[_]`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$DSWordSpecStringWrapper)
-    -
-    @scaladoc[`in` for `F[+_, +_]`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$DSWordSpecStringWrapper2)
-    -
-    @scaladoc[`in` for `F[-_, +_, +_]`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$DSWordSpecStringWrapper3)
+    - @scaladoc[`in` for `F[_]`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$DSWordSpecStringWrapper)
+    - @scaladoc[`in` for `F[+_, +_]`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$DSWordSpecStringWrapper2)
+    - @scaladoc[`in` for `F[-_, +_, +_]`](izumi.distage.testkit.services.scalatest.dstest.DistageAbstractScalatestSpec$$DSWordSpecStringWrapper3)
     - Test cases dependent on injectables: @scaladoc[`Functoid`](izumi.distage.model.providers.Functoid)
 
 ### API Overview
@@ -115,11 +112,13 @@ for defined Plugin modules. See the @ref:[`distage-extension-plugins`](./distage
 more information. For our demonstration the module will be provided using explicit `moduleOverrides` instead of
 classpath scanning, like so:
 
+```scala mdoc:invisible
+```
+
 ```scala mdoc:fakepackage:to-string
 "fakepackage app": Unit
 
 import com.typesafe.config.ConfigFactory
-import distage.config.AppConfigModule
 import distage.ModuleDef
 import izumi.distage.testkit.scalatest.{AssertZIO, Spec3}
 
@@ -133,16 +132,56 @@ abstract class Test extends Spec3[ZIO] with AssertZIO {
   override def config = super
     .config.copy(
       moduleOverrides = new ModuleDef {
-        include(AppConfigModule(ConfigFactory.defaultApplication()))
-
         make[Config].from(defaultConfig)
-        make[Console.Service].fromHas(Console.live)
       },
       debugOutput = true,
     )
 }
 ```
 
+```scala mdoc:invisible
+
+// The goal is to demonstrate testkit plugin integration. `package` is not
+// currently supported in mdoc code. To hack around this the `package app` code
+// blocks are not interpreted and the actual test tested is the one below.
+
+import izumi.distage.plugins.PluginConfig
+import izumi.distage.testkit.services.scalatest.dstest.DistageTestsRegistrySingleton
+
+trait MdocTest extends Test {
+  def name: String
+  override final def suiteName = name
+  override def config = super.config.copy(
+    pluginConfig = PluginConfig.const(BonusServicePlugin)
+  )
+}
+
+object MdocTest {
+  def preRunSetup(): Unit = {
+    DistageTestsRegistrySingleton.resetRegistry()
+  }
+}
+
+final case class SuiteCtor(construct: () => org.scalatest.Suite)
+implicit def suiteCtor(s: => org.scalatest.Suite) = SuiteCtor(() => s)
+
+def __runTest__(suiteCtors: SuiteCtor*) = {
+  // remove all previous tests from registry
+  DistageTestsRegistrySingleton.resetRegistry()
+
+  // run constructors to add the tests to registry
+  val suites = suiteCtors.map(_.construct())
+
+  println("```")
+  suites.foreach {
+    s =>
+      org.scalatest.nostacks.nocolor.run(s)
+      println("\n")
+  }
+  println("```")
+}
+```
+
 #### Test Cases
 
 In `WordSpec`, a test case is a sentence (a `String`) followed by `in` then the body. In
@@ -179,6 +218,7 @@ Let's now create a simple test for our demonstration application:
 "fakepackage app": Unit
 
 class ScoreSimpleTest extends Test {
+
   "Score" should {
 
     "increase by config star value" in {
@@ -191,38 +231,20 @@ class ScoreSimpleTest extends Test {
       assert(actual == expected)
     }
 
-    // Use Config is from the module in the `Test` class above
-    "increase by config start value from DI" in {
+    // Use `Config` from the module in the `Test` class above
+    "increase by config star value from DI" in {
       config: Config =>
         val expected = Score(defaultConfig.starValue)
         val actual = Score.addStar(config, Score.zero)
         assert(actual == expected)
     }
+
   }
 }
 ```
 
 ```scala mdoc:passthrough
-// change this block to `passthrough` instead of `invisible` to view test results.
-// The goal is to demonstrate testkit plugin integration. `package` is not
-// currently supported in mdoc code. To hack around this the `package app` code
-// blocks are not interpreted and the actual test tested is the one below.
-import izumi.distage.plugins.PluginConfig
-
-trait MdocTest extends DummyTest {
-  override def config = super.config.copy(
-    pluginConfig = PluginConfig.cached(Seq(getClass.getPackage.getName))
-  )
-}
-
-object MdocTest {
-  def preRunSetup() = {
-    izumi.distage.testkit.services.scalatest.dstest.DistageTestsRegistrySingleton.resetRegistry()
-  }
-}
-
-MdocTest.preRunSetup()
-org.scalatest.shortstacks.nocolor.run(new ScoreSimpleTest with MdocTest)
+__runTest__(new ScoreSimpleTest with MdocTest { def name = "ScoreSimpleTest" })
 ```
 
 #### Assertions with Effects
@@ -239,8 +261,8 @@ The different effect types fix the `F[_]` argument for this syntax:
 - `Spec3`: `F[Any, Throwable, _]`
 
 With our demonstration application we'll use this to verify the `Score.echoConfig` method. The `Config` required is from
-the `distage` object graph defined in `moduleOverrides`. By using a function from `Config`, the required argument will
-be injected by `distage-testkit`.
+the `distage` object graph defined in `moduleOverrides`. By using a function with a `Config` argument in `in`, the
+required argument will be injected by `distage-testkit`.
 
 ```scala mdoc:fakepackage:to-string
 "fakepackage app": Unit
@@ -248,27 +270,28 @@ be injected by `distage-testkit`.
 class ScoreEffectsTest extends Test {
   "testkit operations with effects" should {
 
-    "assertions in effects" in {
+    "support assertions in effects" in {
       (config: Config) =>
         for {
           actual <- Score.echoConfig(config)
-          _ <- assertIO(actual == config)
+          _      <- assertIO(actual == config)
         } yield ()
     }
 
-    "assertions from effects" in {
+    "support assertions outside of effect" in {
       (config: Config) =>
-        Score.echoConfig(config) map {
-          actual => assert(actual == config)
+        for {
+          actual <- Score.echoConfig(config)
+        } yield {
+          assert(actual == config)
         }
     }
   }
 }
 ```
 
-```scala mdoc:invisible
-MdocTest.preRunSetup()
-org.scalatest.shortstacks.nocolor.run(new ScoreEffectsTest with MdocTest)
+```scala mdoc:passthrough
+__runTest__(new ScoreEffectsTest with MdocTest { def name = "ScoreEffectsTest" })
 ```
 
 #### Assertions with Effects with Environments
@@ -283,61 +306,73 @@ A test that verifies the `BonusService` in our demonstration would be:
 
 abstract class BonusServiceTest extends Test {
   "BonusService" should {
+
     "initially use default bonus as current" in {
       for {
         bonusService <- ZIO.service[BonusService]
         currentBonus <- bonusService.queryCurrentBonus
-        _ <- putStrLn(s"currentBonus = $currentBonus")
-        _ <- assertIO(currentBonus == defaultConfig.defaultBonus)
+        _            <- putStrLn(s"currentBonus = $currentBonus")
+        _            <- assertIO(currentBonus == defaultConfig.defaultBonus)
       } yield ()
     }
 
     "increment by delta" in {
-      val delta = util.Random.nextInt()
       for {
+        delta        <- zio.random.nextInt
         bonusService <- ZIO.service[BonusService]
         initialBonus <- bonusService.queryCurrentBonus
-        actualBonus <- bonusService.increaseCurrentBonus(delta)
+        actualBonus  <- bonusService.increaseCurrentBonus(delta)
         expectedBonus = initialBonus + delta
-        _ <- assertIO(actualBonus == expectedBonus)
+        _            <- assertIO(actualBonus == expectedBonus)
       } yield ()
     }
+
   }
 }
 ```
 
-While this compiles this test cannot be run without the object graph containing a `BonusService`
-resource. For `ZIO[-R, +E, +A]`, the `Has` bindings are injected from `ZLayer`, `ZManaged` `ZIO` or
-any `F[-_, +_, +_]: Local3` (from @ref[BIO](../bio/00_bio.md) typeclasses). See
-@ref[here for details on ZIO Has injection ](basics.md#zio-has-bindings).
+The @ref[ZIO Has injection](basics.md#zio-has-bindings) support extends to the test cases, here we request two components implicitly using the ZIO environment:
 
-Our demonstration application has a dummy and production implementation of the `BonusService`. For each implementation,
-a `ZManaged` is provided. With the `ZManaged` resources added to the object graph test cases can
-inject `Has[BonusService]`.
+- `BonusService` - is requested by `ZIO.service[BonusService]`
+- `zio.Random.Service` - is requested by `zio.random.nextInt`
+
+While this compiles just fine, this test cannot be run without the object graph containing a `BonusService` component!
+
+Our demonstration application includes `dummy` and `production` implementations for `BonusService`.
+For each implementation, we define a `ZManaged` value describing how to create and finalize it.
+After adding implementations for `BonusService` component using these `ZManaged`'s as constructors, our test cases will be able to use the component `BonusService`.
 
 ```scala mdoc:fakepackage:to-string
 "fakepackage app": Unit
 
 object DummyBonusService {
-  class Impl(var bonusValue: Int) extends BonusService {
-    override def queryCurrentBonus = UIO(bonusValue)
-    override def increaseCurrentBonus(delta: Int) = UIO {
-        bonusValue += delta
-        bonusValue
+
+  class Impl(
+     bonusState: Ref[Int]
+  ) extends BonusService {
+
+    override def queryCurrentBonus: UIO[Int] = {
+      bonusState.get
     }
-  }
 
-  val acquire = Task {
-    new Impl(10)
+    override def increaseCurrentBonus(delta: Int): UIO[Int] = {
+      bonusState.updateAndGet(_ + delta)
+    }
   }
 
-  def release: UIO[Unit] = UIO.unit
+  val acquire: Task[DummyBonusService.Impl] = for {
+    ref <- Ref.make(10)
+    impl = new Impl(ref)
+  } yield impl
+
+  val release: UIO[Unit] = UIO.unit
 
-  val managed = acquire.toManaged(_ => release)
+  val managed: TaskManaged[DummyBonusService.Impl] =
+    acquire.toManaged(_ => release)
 }
 ```
 
-This small implementation is useful for verification in both automated tests as well as functional prototypes.
+This dummy implementation is useful for verification in both automated tests and functional prototypes.
 
 For a real system we might build a production implementation like the following. This hypothetical implementation would
 perform an HTTP request to a REST service. We'll introduce a production service, but this actual query will be
@@ -347,42 +382,48 @@ unimplemented for our demonstration:
 "fakepackage app": Unit
 
 object ProdBonusService {
-  class Impl(console: Console.Service, url: String) extends BonusService {
+
+  class Impl(
+    console: Console.Service,
+    url: String,
+  ) extends BonusService {
+
     override def queryCurrentBonus = for {
       _ <- console.putStrLn(s"querying $url")
     } yield ???
+
     override def increaseCurrentBonus(delta: Int) = for {
       _ <- console.putStrLn(s"post to $url")
     } yield ???
   }
 
-  val acquire = for {
+  val acquire: RIO[Has[Console.Service], ProdBonusService.Impl] = for {
     console <- ZIO.service[Console.Service]
-    impl <- Task(new Impl(console, "https://my-bonus-server/current-bonus.json"))
+    impl      = new Impl(console, "https://my-bonus-server/current-bonus.json")
   } yield impl
 
-  def release: UIO[Unit] = UIO {
-    ()
-  }
+  val release: UIO[Unit] = UIO.unit
 
-  val managed = acquire.toManaged(_ => release)
+  val managed: RManaged[Has[Console.Service], ProdBonusService.Impl] =
+    acquire.toManaged(_ => release)
 }
 ```
 
+
 #### Pattern: Dual Test Tactic
 
 The testing of `BonusService` in our demonstration application will follow the Dual Test Tactic. See our blog
 post [Unit, Functional, Integration? You are doing it wrong](https://blog.7mind.io/constructive-test-taxonomy.html) for
 a discussion of test taxonomy and the value of this tactic.
 
-A `ZIO` resource for `BonusService` must be in the `distage` object graph for a `Has[BonusService]` to be injected into
-the `ZIO` environment. One option is to define separate modules for the dummy and production implementations. One module
-would be referenced by tests and the other only by production. However, this is not as useful as both implementations in
-the same object graph but different activations.
+A binding for the implementation of `BonusService` must be passed to `distage`, to be able to build a `Has[BonusService]` to inject into the `ZIO` environment of the test.
+
+But note that we have two implementations, to use both one option is to define separate modules for the dummy and production implementations.
+One module would be used by tests and the other only by production.
+
+However, this is not as declaring both implementations in our modules at the same time but with different activations.
 
-Our demonstration application will use the
-@scaladoc[StandardAxis.Repo](izumi.distage.model.definition.StandardAxis$$Repo$) `Dummy` and `Prod`
-tags:
+Our demonstration application will use the @scaladoc[StandardAxis.Repo](izumi.distage.model.definition.StandardAxis$$Repo$) `Dummy` and `Prod` axis tags:
 
 ```scala mdoc:invisible
 implicit def _hack_whyDoesItNotWorkInMdocHuh_forcedRecompilationToken: izumi.distage.plugins.ForcedRecompilationToken["abc"] = null
@@ -406,11 +447,15 @@ object BonusServicePlugin extends PluginDef {
 }
 ```
 
+Here we used @ref[ZIO Has injection](basics.md#zio-has-bindings) `.fromHas` to supply the environment dependencies for `ProdBonusService.managed`, namely `Has[Console.Service]`.
+(Implementation for `Console.Service` is provided by default from @scaladoc[ZIOSupportModule](izumi.distage.modules.support.ZIOSupportModule))
+`.fromHas` can be used with `ZLayer`, `ZManaged` `ZIO` or any `F[-_, +_, +_]: Local3` (from @ref[BIO](../bio/00_bio.md) typeclasses).
+
 Note that the `BonusServicePlugin` is not explicitly added to the `Test.config`:
-This `PluginDef` is in the same package as the test, namely `app`. By default the `pluginConfig` for the test will
-include the test's package, which will be scanned by `distage` for `PluginDef` instances.
+But, this `PluginDef` class is defined in the same package as the test, namely in `app`. By default the `pluginConfig`
+for the test will include the test's package, which will be scanned by `distage` for `PluginDef` instances.
 
-Continuing with the pattern, a trait will control which repo is activated:
+Continuing with the pattern, a trait will control which implementation is activated:
 
 ```scala mdoc:fakepackage:to-string
 "fakepackage app": Unit
@@ -430,28 +475,23 @@ trait ProdTest extends Test {
 }
 ```
 
-With these a production test and a dummy test can be introduced for the demonstration game score application. Note how
-these are the same scenario, `BonusServiceTest`, but differ in activations.
+With these, a production test and a dummy test can be introduced for the demonstration game score application. Note how
+these share the same test code, in `BonusServiceTest` and differ only in activations.
 
 When extended beyond this small example, this pattern simplifies system level tests, sanity checks, and even a pragmatic
-form of
-[N-Version Programming](https://en.wikipedia.org/wiki/N-version_programming):
+form of [N-Version Programming](https://en.wikipedia.org/wiki/N-version_programming):
 
 ```scala mdoc:fakepackage:to-string
 "fakepackage app": Unit
 
-final class ProdBonusServiceTest extends BonusServiceTest with ProdTest
+class ProdBonusServiceTest extends BonusServiceTest with ProdTest
 
-final class DummyBonusServiceTest extends BonusServiceTest with DummyTest
+class DummyBonusServiceTest extends BonusServiceTest with DummyTest
 ```
 
-<pre>
-```scala mdoc:invisible
-
-val mdocBonusServiceTest = new BonusServiceTest with MdocTest
-org.scalatest.shortstacks.nocolor.run(mdocBonusServiceTest)
+```scala mdoc:passthrough
+__runTest__(new DummyBonusServiceTest with MdocTest { def name = "DummyBonusServiceTest"})
 ```
-</pre>
 
 #### Test Case Context
 
@@ -463,8 +503,9 @@ The `testkit` ScalaTest base classes include the following verbs for establishin
 
 #### Configuration
 
-The test suite class for your application should override the `def config: TestConfig` attributed. The config defines
-the plugin configuration, memoization, module overrides and other options.
+The test suite class for your application should override the `def config: TestConfig` attribute.
+
+`config` defines plugin configuration, memoization, module overrides and other options.
 
 See also:
 
@@ -519,8 +560,8 @@ Provided by trait @scaladoc[AssertSync](izumi.distage.testkit.scalatest.AssertSy
 
 ### Execution Order
 
-By default, tests are executed in parallel. This includes tests using `ZIO`, `monix`, `cats.effect.IO`, and any effect
-type with `cats-effect` or @ref[BIO](../bio/00_bio.md) typeclass instances.
+By default, tests are executed in parallel.
+This includes tests using `ZIO`, `monix`, `cats.effect.IO`, or any effect type with @ref[BIO](../bio/00_bio.md) or `cats-effect` typeclass instances.
 `Identity` is treated as an effect type for imperative code.
 
 Interoperability with all existing Scala effect types is provided by implicit instances of
@@ -605,24 +646,30 @@ class NotUsingMemoTest extends DummyTest {
     )
 
   "Not memoizing BonusService" should {
-    "should use a new instance in the first case" in {
+    "use a new instance in the first case" in {
       val delta = util.Random.nextInt()
 
       for {
         bonusService <- ZIO.service[BonusService]
+        _            <- console.putStrLn(s"\n bonusService = ${bonusService} \n")
+
         // change the bonus service state
         currentBonus <- bonusService.increaseCurrentBonus(delta)
         expectedBonus = defaultConfig.defaultBonus + delta
-        _ <- assertIO(currentBonus == expectedBonus)
+
+        _            <- assertIO(currentBonus == expectedBonus)
       } yield ()
     }
 
-    "and use a new instance in the second case" in {
+    "use a new instance in the second case" in {
       for {
         bonusService <- ZIO.service[BonusService]
+        _            <- console.putStrLn(s"\n bonusService = ${bonusService} \n")
+
         currentBonus <- bonusService.queryCurrentBonus
+
         // verify the state is unchanged from default
-        _ <- assertIO(currentBonus == defaultConfig.defaultBonus)
+        _            <- assertIO(currentBonus == defaultConfig.defaultBonus)
       } yield ()
     }
   }
@@ -633,14 +680,9 @@ These two tests will run sequentially. There is no memoization configured for th
 acquire a fresh instance from the object graph. For our demonstration this results in a new `BonusService` instance for
 each test case.
 
-<pre>
-```scala mdoc:invisible
-// change this block to `passthrough` instead of `invisible` to view test results.
-MdocTest.preRunSetup()
-val mdocNotUsingMemoTest = new NotUsingMemoTest with MdocTest
-org.scalatest.shortstacks.nocolor.run(mdocNotUsingMemoTest)
+```scala mdoc:passthrough
+__runTest__(new NotUsingMemoTest with MdocTest { def name = "NotUsingMemoTest" })
 ```
-</pre>
 
 Configuring the test to memoize `BonusService` will result in the same instance being used for both test cases:
 
@@ -660,25 +702,29 @@ class UsingMemoTest extends DummyTest {
   val delta = util.Random.nextInt()
 
   "Memoizing BonusService" should {
-    "should use a new instance in the first case" in {
+    "use a new instance in the first case" in {
       for {
         bonusService <- ZIO.service[BonusService]
-        _ <- console.putStrLn(s"bonusService = ${bonusService}")
+        _            <- console.putStrLn(s"\n bonusService = ${bonusService} \n")
+
         // change the bonus service state
         currentBonus <- bonusService.increaseCurrentBonus(delta)
         expectedBonus = defaultConfig.defaultBonus + delta
-        _ <- assertIO(currentBonus == expectedBonus)
+
+        _            <- assertIO(currentBonus == expectedBonus)
       } yield ()
     }
 
-    "and use the same instance in the second case" in {
+    "use the same instance in the second case" in {
       for {
         bonusService <- ZIO.service[BonusService]
-        _ <- console.putStrLn(s"bonusService = ${bonusService}")
+        _            <- console.putStrLn(s"\n bonusService = ${bonusService} \n")
+
         currentBonus <- bonusService.queryCurrentBonus
         expectedBonus = defaultConfig.defaultBonus + delta
+
         // verify the change in the first case modified this bonusService
-        _ <- assertIO(currentBonus == expectedBonus)
+        _            <- assertIO(currentBonus == expectedBonus)
       } yield ()
     }
   }
@@ -687,15 +733,16 @@ class UsingMemoTest extends DummyTest {
 
 The memoization roots include `BonusService`. This results in the same `BonusService` instance for each test case.
 
-This test requires the effect of the first test case to occur prior to the second test case. As
-discussed [Execution Order section](#execution-order): Without configuring test cases for sequential parallel level this
-order would not be guaranteed.
+This test requires the effect of the first test case to occur prior to the second test case.
+As discussed [Execution Order section](#execution-order): Without configuring test cases for sequential execution this order would not be guaranteed.
+
+Note that this test will *not* use the same `BonusService` instance as `NotUsingMemoTest`.
+The configs for these test have different memoization roots.
+This results in different [memoization environments](#memoization-environments).
 
-Note that the test did *not* use the same `BonusService` instance as `NotUsingMemoTest`. The config for each test has
-different memoization roots. This results in different [memoization environments](#memoization-environments).
+If the memoization environments are equal then the components will be shared.
 
-If the memoization environments are equal then the components will be shared. For our example, any other test with the
-same memoization environment would use the same `BonusService` instance.
+For our example, any other test suite with the same memoization environment will share the same `BonusService` instance:
 
 ```scala mdoc:fakepackage
 "fakepackage app": Unit
@@ -713,38 +760,23 @@ class AnotherUsingMemoTest extends DummyTest {
     "use the same instance" in {
       for {
         bonusService <- ZIO.service[BonusService]
-        _ <- console.putStrLn(s"bonusService = ${bonusService}")
+        _            <- console.putStrLn(s"\n bonusService = ${bonusService} \n")
         currentBonus <- bonusService.queryCurrentBonus
-        _ <- console.putStrLn(s"currentBonus = ${currentBonus}")
+        _            <- console.putStrLn(s"currentBonus = ${currentBonus}")
       } yield ()
     }
   }
 }
 ```
 
-Both tests, all three test cases, will use same memoization environment and `bonusService` instance:
+Both tests suites, all three test cases, will use same memoization environment and the same `bonusService` instance:
 
+```scala mdoc:passthrough
+__runTest__(
+  new UsingMemoTest with MdocTest { def name = "UsingMemoTest" },
+  new AnotherUsingMemoTest with MdocTest { def name = "AnotherUsingMemoTest" },
+)
 ```
-<from logging>
-[info] phase=late, memoEnv=407164314 Memoization environment with suites=1 tests=2 test_suites=
-<from console>
-bonusService = repl.MdocSession$App$DummyBonusService$Impl@2843b83b
-bonusService = repl.MdocSession$App$DummyBonusService$Impl@2843b83b
-bonusService = repl.MdocSession$App$DummyBonusService$Impl@2843b83b
-```
-
-<pre>
-```scala mdoc:invisible
-// change this block to `passthrough` instead of `invisible` to view test results.
-MdocTest.preRunSetup()
-val mdocUsingMemoTest = new UsingMemoTest with MdocTest
-val mdocAnotherUsingMemoTest = new AnotherUsingMemoTest with MdocTest
-// while this looks to only run one test this will run both.
-org.scalatest.shortstacks.nocolor.run(mdocUsingMemoTest)
-// this only outputs the trace of the second tests run
-org.scalatest.shortstacks.nocolor.run(mdocAnotherUsingMemoTest)
-```
-</pre>
 
 #### Pseudocode
 
@@ -772,35 +804,37 @@ for each test case
 
 ### Forced Roots
 
-The `forcedRoots` of `TestConfig` specifies components added to the dependencies of every test within this memoization
-environment. Without memoization these are acquired and release each test case. With memoization these are acquired
-before all and released after all tests within this memoization environment.
+`forcedRoots` field of `TestConfig` specifies components to synthetically add to the dependencies of every test within this test suite / memoization environment.
+
+If forced root components are not memoized, they will be acquired and released for each test case.
+
+If memoized, they will be acquired and released once, before all and after all the tests within this memoization environment.
+
+They provide an alternative to ScalaTest's native `beforeEach/beforeAll` that can use functional effects instead of mutability (However, `All` here includes the entire memoization environment, not the enclosing test suite)
 
 ### Test Selection
 
 #### Using `IntegrationCheck`
 
-Implementation classes that inherit from
-@scaladoc[`izumi.distage.framework.model.IntegrationCheck`](izumi.distage.framework.model.IntegrationCheck)
-will implement a `resourceCheck()` method that will be called before the test instantiation to check if external test
-dependencies (such as Docker containers in
-@ref[distage-framework-docker](distage-framework-docker.md#docker-test-resources)) are available for the test (or role, when in main scope).
+Implementation classes that inherit from @scaladoc[`izumi.distage.framework.model.IntegrationCheck`](izumi.distage.framework.model.IntegrationCheck)
+will have their `resourceCheck()` method called before the test instantiation to check if external test dependencies —
+such as Docker containers in @ref[distage-framework-docker](distage-framework-docker.md#docker-test-resources) —
+are available for the test (or for the role when in main scope).
+
 If not, the test will be canceled/ignored.
 
-This feature allows you to therefore selectively run only the fast in-memory tests that have no external dependencies.
-Integration checks are executed only in `distage-testkit` tests and `distage-framework`'s
-@ref[Roles](distage-framework.md#roles).
+This feature therefore allows you to selectively run only the fast in-memory tests that have no external dependencies
+by shutting down the docker daemon (or another source of external dependencies).
+
+Integration checks are executed only in `distage-testkit` tests and `distage-framework` @ref[roles](distage-framework.md#roles).
 
 ### References
 
+- [distage Example Project](https://github.com/7mind/distage-example) project shows how to use `distage`, `distage-testkit` & `distage-framework-docker`
+- Video for [Hyper-pragmatic Pure FP Testing with distage-testkit](https://www.youtube.com/watch?v=CzpvjkUukAs) – is an overview of the concepts, design and usage.
 - Slides for [Hyper-pragmatic Pure FP testing with distage-testkit](https://www.slideshare.net/7mind/hyperpragmatic-pure-fp-testing-with-distagetestkit)
-- Slides for [Scala, Functional Programming and Team Productivity
-  ](https://www.slideshare.net/7mind/scala-functional-programming-and-team-productivity)
-- [distage Example Project](https://github.com/7mind/distage-example) project shows how to use
-  `distage`, `distage-testkit` & `distage-framework-docker`
-- The [Hyper-pragmatic Pure FP Testing with distage-testkit](https://www.youtube.com/watch?v=CzpvjkUukAs) talk is an
-  overview of the concepts, design and usage.
-- 7mind's blog [Constructive Test Taxonomy](https://blog.7mind.io/constructive-test-taxonomy.html)
+- Slides for [Scala, Functional Programming and Team Productivity](https://www.slideshare.net/7mind/scala-functional-programming-and-team-productivity)
+- 7mind blog [Constructive Test Taxonomy](https://blog.7mind.io/constructive-test-taxonomy.html)
 - [N-Version Programming](https://en.wikipedia.org/wiki/N-version_programming)
 
 ## Extended Example