ZIO TestClock for a shared environment

92 views Asked by At

It looks like manipulating ZIO TestClock does not work for services that are provided as shared layers. Here is an example to demonstrate it:

  case class TimerService(in: Queue[Int], out: Queue[Instant]) {
    def serve = ZStream.fromQueue(in)
      .takeWhile(_ > 0)
      .foreach { _ => Clock.instant.flatMap(out.offer _) }

    def ask(n: Int) = in.offer(n) *> out.take.map(_.toEpochMilli / 1000)
  }

  object TimerService {
    def fork = for {
      in <- Queue.bounded[Int](1)
      out <- Queue.bounded[Long](1)
      ts = TimerService(in, out)
      _ <- ts.serve.fork
    } yield ts
  }

  ...
  def spec: {
    suite("Shared Timer Service") ( 
     test("this does not work!") {
      for {
          ts <- ZIO.service[TimerService]
          answer <- TestClock.adjust(1.second) *> ts.ask(1)
      } yield assertTrue(answer == 1)
     }
    ).provideSomeLayerShared(ZLayer.fromZIO(TimerService.fork))
     .provideSomeLayer(ZLayer.fromZIO(TestClock.setTime(Instant.ofEpochMilli(0)))) +
    
   suit("Own timer service") (
    test("this does!") {
       for { 
         ts <- ZIO.service[TimerService]
         answer <- TestClock.adjust(1.second) *> ts.ask(1)
      } yield assertTrue(answer == 1)
    }
   ).provideSomeLayer(ZLayer.fromZIO(TimerService.fork))
    .provideSomeLayer(ZLayer.fromZIO(TestClock.setTime(Instant.ofEpochMilli(0))))
 }

So, the service basically listens to requests on a queue and responds with what it thinks is current timestamp on another queue. The test forks the service, advances the clock by one second, then asks the service what time it is.

The first of the tests fails (the response is 0 instead of 1 – the clock is never advanced even though the initial time is properly set), the second one succeeds. The only difference is that in the fist case the forked service layer is Shared and in the second one it is not. The idea is to fork a service instance once, and use it for all the tests, rather than spawning a separate one every time. Is it not possible?

1

There are 1 answers

4
Lachezar On

You can use a TestAspect.before to make a change in the environment in a specific test case (that will not affect other test cases).

E.g.

... @@ TestAspect.before(TestClock.adjust(1.second))

There is an old article that sheds some more light on this topic, I recommend to read it - https://hmemcpy.com/2021/11/working-with-shared-dependencies-in-zio-test/

In an essence a shared layer is constructed once and copied for all tests (you gain performance if the construction is expensive), but you can't modify it inside your tests.

The only possible way to modify it is through TestAspects.