Search code examples
scalafunctional-programmingscala-cats

What type to use in tests which require Concurrent and Timer type class instance?


Consider the following example:

import cats.Functor
import cats.effect.{Concurrent, Timer}
import cats.syntax.functor._
import fs2.Stream

import scala.concurrent.duration._

class FetchAndSum[F[_]: Timer: Concurrent: Functor](fetch: List[String] => F[List[Int]]) {

  def run(inputs: List[String]): F[Int] =
    Stream
      .emits(inputs)
      .covary[F]
      .groupWithin(20, 10.millis)
      .mapAsync(10)(chunk => fetch(chunk.toList))
      .flatMap(Stream.emits)
      .reduce(_ + _)
      .compile
      .last
      .map(_.getOrElse(0))
}

In production this is instantiated with the IO Monad.

In my tests I would like to test how many times the fetch function gets called. If F[_] would require only a Functor instance, I could do that simply with the Writer monad.

Due to the mapAsync and groupedWithin of fs2, F[_] must also have instances of Timer and Concurrent, those of course do not exist on Writer.

What data type could I use to test this in a functional way?

I thought about somehow combining an IO with a Writer e.g. type IOWriter[A] = IO[Writer[Int, A]], but I was not able to make that work without redeclaring all the type class instances for IOWriter.

Is there something which allows me to achieve that without having to redeclare all the type class instances?


Solution

  • Use IO with Ref:

    val numsExecuted: IO[Int] = for {
      ref <- Ref[IO].of(0)
      fetch = (l: List[String]) => ref.update(_ + 1).as(???)
      _ <- new FetchAndSum[IO](fetch).run(???)
      x <- ref.get
    } yield x
    

    You can also use Writer combined with IO. This construct is known as Writer monad transformer (type IOWriter[A] = cats.data.WriterT[IO, A]) and should have Concurrent / Timer / Monad / etc. instances out of the box.