scalaziozio-test

How to extend the TestEnvironment of a ZIO Test


I want to test the following function:

def curl(host: String, attempt: Int = 200): ZIO[Loggings with Clock, Throwable, Unit]

If the environment would just use standard ZIO environments, like Console with Clock, the test would work out of the box:

testM("curl on valid URL") {
      (for {
        r <- composer.curl("https://google.com")
      } yield
        assert(r, isUnit))
    }

The Test environment would be provided by zio-test.

So the question is, how to extend the TestEnvironment with my Loggings module?


Solution

  • Note that this answer is for RC17 and will change significantly in RC18. You're right that as in other cases of composing environments we need to implement a function to build our total environment from the modules we have. Spec has several combinators built in such as provideManaged to do this so you don't need to do it within your test itself. All of these have "normal" variants that will provide a separate copy of the environment to each test in a suite and "shared" variants that will create one copy of the environment for the entire suite when it is a resource that is expensive to create like a Kafka service.

    You can see an example below of using provideSomeManaged to provide an environment that extends the test environment to a test.

    In RC18 there will be a variety of other provide variants equivalent to those on ZIO as well as a new concept of layers to make it much easier to build composed environments for ZIO applications.

    import zio._
    import zio.clock._
    import zio.test._
    import zio.test.environment._
    
    import ExampleSpecUtil._
    
    object ExampleSpec
        extends DefaultRunnableSpec(
          suite("ExampleSpec")(
            testM("My Test") {
              for {
                time <- clock.nanoTime
                _ <- Logging.logLine(
                  s"The TestClock says the current time is $time"
                )
              } yield assertCompletes
            }
          ).provideSomeManaged(testClockWithLogging)
        )
    
    object ExampleSpecUtil {
    
      trait Logging {
        def logging: Logging.Service
      }
    
      object Logging {
    
        trait Service {
          def logLine(line: String): UIO[Unit]
        }
    
        object Live extends Logging {
          val logging: Logging.Service =
            new Logging.Service {
              def logLine(line: String): UIO[Unit] =
                UIO(println(line))
            }
        }
    
        def logLine(line: String): URIO[Logging, Unit] =
          URIO.accessM(_.logging.logLine(line))
      }
    
      val testClockWithLogging
          : ZManaged[TestEnvironment, Nothing, TestClock with Logging] =
        ZIO
          .access[TestEnvironment] { testEnvironment =>
            new TestClock with Logging {
              val clock = testEnvironment.clock
              val logging = Logging.Live.logging
              val scheduler = testEnvironment.scheduler
            }
          }
          .toManaged_
    }