cats.effect.kernel.testkit

Type members

Classlikes

trait ApplicativeGenerators[F[_]] extends Generators1[F]
trait AsyncGenerators[F[_]] extends GenTemporalGenerators[F, Throwable] with SyncGenerators[F]
trait ClockGenerators[F[_]] extends Generators1[F]
trait FreeSyncEq
trait GenK[F[_]]
trait GenSpawnGenerators[F[_], E] extends MonadCancelGenerators[F, E]
trait GenTemporalGenerators[F[_], E] extends GenSpawnGenerators[F, E] with ClockGenerators[F]
trait Generators1[F[_]]
trait MonadCancelGenerators[F[_], E] extends MonadErrorGenerators[F, E]
Companion
object
Companion
class
trait SyncGenerators[F[_]] extends MonadErrorGenerators[F, Throwable] with ClockGenerators[F]
Companion
object
final class TestContext extends ExecutionContext

A scala.concurrent.ExecutionContext implementation and a provider of cats.effect.Timer instances, that can simulate async boundaries and time passage, useful for testing purposes.

A scala.concurrent.ExecutionContext implementation and a provider of cats.effect.Timer instances, that can simulate async boundaries and time passage, useful for testing purposes.

Usage for simulating an ExecutionContext):

 implicit val ec = TestContext()

 ec.execute(new Runnable { def run() = println("task1") })

 ex.execute(new Runnable {
   def run() = {
     println("outer")

     ec.execute(new Runnable {
       def run() = println("inner")
     })
   }
 })

 // Nothing executes until `tick` gets called
 ec.tick()

 // Testing the resulting state
 assert(ec.state.tasks.isEmpty)
 assert(ec.state.lastReportedFailure == None)

Our TestContext can also simulate time passage, as we are able to builds a cats.effect.Timer instance for any data type that has a LiftIO instance:

 val ctx = TestContext()

 val timer: Timer[IO] = ctx.timer[IO]

We can now simulate actual time:

 val io = timer.sleep(10.seconds) *> IO(1 + 1)
 val f = io.unsafeToFuture()

 // This invariant holds true, because our IO is async
 assert(f.value == None)

 // Not yet completed, because this does not simulate time passing:
 ctx.tick()
 assert(f.value == None)

 // Simulating time passing:
 ctx.tick(10.seconds)
 assert(f.value == Some(Success(2))

Simulating time makes this pretty useful for testing race conditions:

 val never = IO.async[Int](_ => {})
 val timeoutError = new TimeoutException
 val timeout = timer.sleep(10.seconds) *> IO.raiseError[Int](timeoutError)

 val pair = (never, timeout).parMapN(_ + _)

 // Not yet
 ctx.tick()
 assert(f.value == None)
 // Not yet
 ctx.tick(5.seconds)
 assert(f.value == None)

 // Good to go:
 ctx.tick(5.seconds)
 assert(f.value, Some(Failure(timeoutError)))
Companion
object
object TestContext
Companion
class
final case class TestException(i: Int) extends Exception
object freeEval extends FreeSyncEq
object pure