EffectTestSuite[F].scala

| comments

Helpers for ScalaTest for testing effects (e.g. cats.effect.IO, monix.eval.Task). This is similar to PureApp.

Requirement: EffectRuntime (snippet)

Usage:

object Fns {
  def fireMissiles[F[_]: Sync]: F[Int] =
    Sync[F].delay {
      println("Firing missiles...")
      Random.nextInt(100) + 1
    }
}

class MyTestSuite extends EffectTestSuite.ForIO {
  testEffect("fire missiles") {
    for {
      count <- Fns.fireMissiles
    } yield {
      assert(count > 0)
    }
  }
}

Implementation:

import cats.effect._
import org.scalactic.source
import org.scalatest.compatible.Assertion
import org.scalatest.funsuite.AsyncFunSuiteLike
import org.scalatest.{ BeforeAndAfterAll, Tag }
import scala.concurrent.Promise
import scala.concurrent.duration._

trait EffectTestSuite[F[_]] extends AsyncFunSuiteLike {
  protected def effectTimeout: FiniteDuration = 30.seconds

  protected def testEffect(testName: String, testTags: Tag*)(
    testFun: => F[Assertion]
  )(implicit F: ConcurrentEffect[F], timer: Timer[F], pos: source.Position): Unit = {
    test(testName, testTags: _*) {
      val task = Concurrent.timeout(testFun, effectTimeout)
      val p = Promise[Assertion]()
      F.runAsync(task)(r => F.delay { p.complete(r.toTry); () }
      p.future
    }
  }
}

object EffectTestSuite {
  /** For working with `cats.effect.IO`
    */
  trait ForIO extends EffectTestSuite[IO] with BeforeAndAfterAll {
    protected def runtimeFactory: Resource[SyncIO, EffectRuntime[IO]] = {
      // This should be a concrete implementation
      ???
    }

    protected final lazy val (runtimeRef, runtimeCancel) = {
      runtimeFactory
        .allocated
        .unsafeRunSync()
    }

    override protected def beforeAll(): Unit = {
      super.beforeAll()
      // forces initialization
      runtimeRef; ()
    }

    override protected def afterAll(): Unit = {
      super.afterAll()
      runtimeCancel.unsafeRunSync()
    }

    protected implicit lazy val contextShift: EffectRuntime[IO] =
      runtimeRef

    protected implicit lazy val F: ConcurrentEffect[IO] =
      IO.ioConcurrentEffect(contextShift)

    protected implicit lazy val timer: Timer[IO] =
      runtimeRef.timer(F)
  }
}
| Written by