Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 382
regexp Avatar asked Mar 22 '26 02:03

regexp


1 Answers

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.

like image 195
Oleg Pyzhcov Avatar answered Mar 23 '26 16:03

Oleg Pyzhcov