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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With