Skip to content

Instantly share code, notes, and snippets.

@zainab-ali
Created October 1, 2025 14:05
Show Gist options
  • Save zainab-ali/ceaf90cb04773262468f3d369e1fc0ec to your computer and use it in GitHub Desktop.
Save zainab-ali/ceaf90cb04773262468f3d369e1fc0ec to your computer and use it in GitHub Desktop.
Demonstrates usage of `Provider` vs `SharedResourceSuite`
//> using scala "3.7.0"
//> using test.dep org.typelevel::weaver-cats::0.11-5acfd6a-SNAPSHOT
//> using repository "https://central.sonatype.com/repository/maven-snapshots"
import cats.effect.*
import cats.*
import cats.data.OptionT
import weaver.*
import fs2.*
type MyEffect[A] = OptionT[IO, A]
val asyncInstance = implicitly[Async[MyEffect]]
val parallelInstance = implicitly[Parallel[MyEffect]]
// This demonstrates how `getSuite` might be used to construct tests with different effect types.
abstract class MyOldSuite extends MutableFSuite[MyEffect] with BaseCatsSuite {
outer =>
// The Async instance for OptionT causes a deadlock in the stream on parEvalMap.
// Set the concurrency to 1 so evalMap is used instead.
override def maxParallelism: Int = 1
type Res = Unit
def sharedResource: Resource[MyEffect, Unit] = Resource.pure(())
// The user has to declare an UnsafeRun instance for their effect type
implicit protected def effectCompat: UnsafeRun[MyEffect] =
new UnsafeRun[MyEffect] {
implicit def effect: cats.effect.kernel.Async[MyEffect] = asyncInstance
implicit def parallel: cats.Parallel[MyEffect] = parallelInstance
type CancelToken = CatsUnsafeRun.CancelToken
def background(task: MyEffect[Unit]): CancelToken =
CatsUnsafeRun.background(task.value.as(()))
def cancel(token: CancelToken): Unit = CatsUnsafeRun.cancel(token)
def unsafeRunAndForget(task: MyEffect[Unit]): Unit =
CatsUnsafeRun.unsafeRunAndForget(task.value.as(()))
def unsafeRunSync(task: MyEffect[Unit]): Unit =
CatsUnsafeRun.unsafeRunSync(task.value.as(()))
def unsafeRunToFuture(
task: MyEffect[Unit]): scala.concurrent.Future[Unit] =
CatsUnsafeRun.unsafeRunToFuture(task.value.as(()))
}
// If any tests return None, discard all test outcomes.
// This is unlikely to be what users will implement, but I'm not
// sure what the real use cases of OptionT are.
def getSuite: weaver.EffectSuite[IO] = new EffectSuite[IO] {
implicit protected def effectCompat: UnsafeRun[IO] = CatsUnsafeRun
def spec(args: List[String]): Stream[IO, TestOutcome] = {
val maybeOutcomes = outer.spec(args).compile.toList.value
Stream.evals(maybeOutcomes.map(_.getOrElse(Nil)))
}
}
}
// A usage example. None of the tests are reported due to the OptionT.none instance.
object MyOldTest extends MyOldSuite with Expectations.Helpers {
test("Should not be reported")(OptionT.none[IO, Expectations])
test("Should also not be reported")(OptionT(IO(Some(success))))
test("Should also also not be reported")(OptionT(IO(Some(failure("fail!")))))
}
// This demonstrates how `SharedResourceSuite` can be used instead.
abstract class MyNewSuite extends SharedResourceSuite[IO] with BaseIOSuite {
type Res = Unit
def sharedResource: Resource[IO, Unit] = Resource.pure(())
// The user would define how to transform their effect into IO
private def transformMyEffectToIO: MyEffect[Expectations] => IO[Expectations] =
_.value.flatMap {
case None => Expectations.Helpers.ignore("Ignoring")
case Some(exp) => IO(exp)
}
// Define a DSL for registering tests using a different effect type
def test(name: TestName)(run: MyEffect[Expectations]): Unit =
registerTest(name)(_ => Test(name.name, transformMyEffectToIO(run)))
}
// A usage example. Tests returning an OptionT.none instance are reported as ignored.
object MyNewTest extends MyNewSuite with Expectations.Helpers {
test("Should be ignored")(OptionT.none)
test("Should succeed")(OptionT(IO(Some(success))))
test("Should fail")(OptionT(IO(Some(failure("fail!")))))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment