cats-effect-testing
A quickie little utility which makes it easier to write tests using specs2 (mutable or functional), scalatest, µTest or minitest where the examples are effectful within cats.effect.IO. The Specs2 support is generalized to other Effects, such as Monix Task and ZIO.
Specs2
import cats.effect.IO
import cats.effect.testing.specs2.CatsIO
import org.specs2.mutable.Specification
// for some reason, only class works here; object will not be detected by sbt
class ExampleSpec extends Specification with CatsIO {
"examples" should {
"do the things" in IO {
true must beTrue
}
}
}
The above compiles and runs exactly as you would expect.
By default, tests run with a 10 second timeout. If you wish to override this, simply override the inherited Timeout val:
override val Timeout = 5.seconds
If you need an ExecutionContext, one is available in the executionContext val.
Usage
libraryDependencies += "com.codecommit" %% "cats-effect-testing-specs2" % "<version>" % Test
Published for Scala 2.13 and 2.12. Depends on cats-effect 2.1.0 and specs2 4.7.1.
ScalaTest
import cats.effect._
import cats.effect.testing.scalatest.AsyncIOSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.freespec.AsyncFreeSpec
class MySpec extends AsyncFreeSpec with AsyncIOSpec with Matchers {
"My Code " - {
"works" in {
IO(1).asserting(_ shouldBe 1)
}
}
Usage
libraryDependencies += "com.codecommit" %% "cats-effect-testing-scalatest" % "<version>" % Test
Published for Scala 2.13 and 2.12. Depends on cats-effect 2.1.0 and scalatest 3.1.0.
ScalaTest ScalaCheck
The module provides an instance of the org.scalatestplus.scalacheck.CheckerAsserting, which can be used with any type of effect that has an instance of cats.effect.Effect: IO, EitherT[IO, Throwable, *], and so on.
import cats.data.EitherT
import cats.effect.testing.scalatest.AsyncIOSpec
import cats.effect.{IO, Sync}
import org.scalatest.matchers.should.Matchers
import org.scalatestplus.scalacheck.{CheckerAsserting, ScalaCheckPropertyChecks}
class MySpec extends AsyncIOSpec with Matchers with ScalaCheckPropertyChecks {
"My IO Code" - {
"works with effect-full property-based testing" in {
forAll { (l1: List[Int], l2: List[Int]) =>
IO.delay(l1.size + l2.size shouldBe (l1 ::: l2).size)
}
}
implicit def ioCheckingAsserting[A]: CheckerAsserting[IO[A]] { type Result = IO[Unit] } =
new EffectCheckerAsserting
}
"My EitherT[IO, Throwable, A] code" - {
type Eff[A] = EitherT[IO, Throwable, A]
"works with effect-full property-based testing" in {
val check = forAll { (l1: List[Int], l2: List[Int]) =>
Sync[Eff].delay(l1.size + l2.size shouldBe (l1 ::: l2).size)
}
check.leftSemiflatMap[Unit](IO.raiseError).merge.assertNoException
}
implicit def checkingAsserting[A]: CheckerAsserting[Eff[A]] { type Result = Eff[Unit] } =
new EffectCheckerAsserting
}
}
Usage
libraryDependencies += "com.codecommit" %% "cats-effect-testing-scalatest-scalacheck" % "<version>" % Test
Published for Scala 2.13 and 2.12. Depends on cats-effect 2.1.0, scalatest-scalacheck-1-15 3.2.2.0, and scalacheck 1.15.1.
µTest
import scala.concurrent.duration._
import utest._
import cats.implicits._
import cats.effect.IO
import cats.effect.testing.utest.{IOTestSuite, DeterministicIOTestSuite}
// IOTestSuite uses real ExecutionContext for async operations
object SimpleSuite extends IOTestSuite {
override val timeout = 1.second // Default timeout is 10 seconds
val tests = Tests {
test("do the thing") {
IO(assert(true))
}
}
}
// DeterministicIOTestSuite simulates time with TestContext from cats-effect-laws
// package. That allows to simulate long timeouts and have async operations
// without actually slowing down your test suite, but it cannot use operations
// that are hard-wired to do real async calls
object DetSuite extends DeterministicIOTestSuite {
// By default, both types of suite prevents using non-IO return values.
// I recommend separating effectful and pure suites altogether, but
// this can be overriden like so:
override val allowNonIOTests = true
val tests = Tests {
test("Simulated time!") {
IO.sleep(8.hours) >> IO(assert(!"life".isEmpty))
}
test("Non-IO tests") {
assert(true)
}
}
}
Usage
libraryDependencies += "com.codecommit" %% "cats-effect-testing-utest" % "<version>" % Test
Published for Scala 2.13 and 2.12. Depends on cats-effect 2.1.0 and µTest 0.7.1.
Minitest
Minitest is very similar to uTest, but being strongly typed, there's no need to support non-IO tests
import scala.concurrent.duration._
import cats.implicits._
import cats.effect.IO
import cats.effect.testing.minitest.{IOTestSuite, DeterministicIOTestSuite}
// IOTestSuite uses real ExecutionContext for async operations
// (can be overriden by reimplementing makeExecutionContext)
object SimpleSuite extends IOTestSuite {
override val timeout = 1.second // Default timeout is 10 seconds
test("do the thing") {
IO(assert(true))
}
}
// DeterministicIOTestSuite simulates time with TestContext from cats-effect-laws
// package. That allows to simulate long timeouts and have async operations
// without actually slowing down your test suite, but it cannot use operations
// that are hard-wired to do real async calls
object DetSuite extends DeterministicIOTestSuite {
test("Simulated time!") {
IO.sleep(8.hours) >> IO(assert(!"life".isEmpty))
}
}
Usage
libraryDependencies += "com.codecommit" %% "cats-effect-testing-minitest" % "<version>" % Test
Published for Scala 2.13 and 2.12. Depends on cats-effect 2.0.0 and minitest 2.7.0.