Testing with cats-effect

Compatible libraries

Relatively few libraries support cats-effect directly at this time. However, most (if not all) popular testing frameworks have libraries offering some level of integration

Property-based Testing


Scalacheck primarily supports properties in the shape A => Assertion. To support writing effectful properties with the shape A => F[Assertion], you can use one of these tools:

You might also want to use cats-scalacheck, which provides instances of cats-core typeclasses.

Best practices

Avoid using unsafe* methods in tests, the same as you’d avoid them in “main” code. Writing tests to be structured the same way as “normal” code results in tests that are less likely to be flaky, act as executable documentation, and remain easy to read.

Use a compatible framework’s support for writing IO[Assertion] style tests.

Testing concurrency

In many test cases, TestContext should be used as an ExecutionContext, ContextShift, and Timer. This gives you very precise control over the sequencing and scheduling of evaluation, making it possible to deterministically replicate certain race conditions or even identify timing-related bugs without having to rely on non-deterministic thread ordering. TestContext also gives you control over time (as exposed by Timer and Clock), which makes it easy to write unit tests for time-related semantics without having to rely on sleeps or other hacks.

To simulate time passing in a test, use Timer[F].sleep(duration), which will defer to TestContext.

Be aware though that TestContext is a very artificial environment, and it can in turn mask bugs that a realistic executor would uncover. The most comprehensive test suites will often use a balance of both TestContext and a more real-world thread pool implementation.

Some reference material on this approach:

Managing shared resources

Sometimes you’ll want to write a test that acquires a Resource before the suite and releases it after. For example, spinning up a database.

With the Weaver test framework, this is supported using cats-effect Resource out of the box.

distage-testkit test framework extends the usefulness of Resource further, allowing to designate resources to be acquired only once globally for all test suites or for a subset of test suites. (Resource Reuse - Memoization)

For other test frameworks that use imperative “hook”-style methods (such as scalatest’s BeforeAndAfterAll mixin), you can use allocated

class Database {
    def close(): Unit = ???

object Database {
  def resource: Resource[IO, Database] = Resource.make(IO(new Database))(d => IO(d.close()))

class TestSuite {
  private var _database: Option[(Database, IO[Unit])] = None
  private def database: Database = _database.getOrElse(sys.error("not currently alive!"))._1

  def beforeAll: Unit = {
    _database = Some(Database.resource.allocated.unsafeRunSync())

  def afterAll: Unit = {
    _database = None

  /* tests using `database` */
  def test = {
      assert(database != null)