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?
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.