Repository: scala-steward-org/scala-steward
Pull request #2351 (2021-11-14 13:53:52)

Use Resource for temporarily creating and removing files
Using `Resource` makes it easier to compose those functions compared to
nesting `F[A] => F[A]`.

codecov[bot]: # [Codecov](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org) Report
> Merging [#2351](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org) (6282c4e) into [master](https://codecov.io/gh/scala-steward-org/scala-steward/commit/02d124b91d22190e1ad81c5b281034d1e290ddf8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org) (02d124b) will **decrease** coverage by `0.06%`.
> The diff coverage is `88.23%`.

[![Impacted file tree graph](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351/graphs/tree.svg?width=650&height=150&src=pr&token=udzMsc1Dne&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org)](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org)

```diff
@@            Coverage Diff             @@
##           master    #2351      +/-   ##
==========================================
- Coverage   80.25%   80.19%   -0.07%     
==========================================
  Files         138      137       -1     
  Lines        2391     2373      -18     
  Branches       60       55       -5     
==========================================
- Hits         1919     1903      -16     
+ Misses        472      470       -2     
```


| [Impacted Files](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org) | Coverage Δ | |
|---|---|---|
| [...g/scalasteward/core/buildtool/maven/MavenAlg.scala](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org#diff-bW9kdWxlcy9jb3JlL3NyYy9tYWluL3NjYWxhL29yZy9zY2FsYXN0ZXdhcmQvY29yZS9idWlsZHRvb2wvbWF2ZW4vTWF2ZW5BbGcuc2NhbGE=) | `87.50% <66.66%> (ø)` | |
| [...a/org/scalasteward/core/buildtool/sbt/SbtAlg.scala](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org#diff-bW9kdWxlcy9jb3JlL3NyYy9tYWluL3NjYWxhL29yZy9zY2FsYXN0ZXdhcmQvY29yZS9idWlsZHRvb2wvc2J0L1NidEFsZy5zY2FsYQ==) | `97.29% <88.88%> (+1.29%)` | :arrow_up: |
| [...org/scalasteward/core/application/StewardAlg.scala](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org#diff-bW9kdWxlcy9jb3JlL3NyYy9tYWluL3NjYWxhL29yZy9zY2FsYXN0ZXdhcmQvY29yZS9hcHBsaWNhdGlvbi9TdGV3YXJkQWxnLnNjYWxh) | `40.00% <100.00%> (ø)` | |
| [...org/scalasteward/core/buildtool/mill/MillAlg.scala](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org#diff-bW9kdWxlcy9jb3JlL3NyYy9tYWluL3NjYWxhL29yZy9zY2FsYXN0ZXdhcmQvY29yZS9idWlsZHRvb2wvbWlsbC9NaWxsQWxnLnNjYWxh) | `86.95% <100.00%> (ø)` | |
| [.../main/scala/org/scalasteward/core/io/FileAlg.scala](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org#diff-bW9kdWxlcy9jb3JlL3NyYy9tYWluL3NjYWxhL29yZy9zY2FsYXN0ZXdhcmQvY29yZS9pby9GaWxlQWxnLnNjYWxh) | `96.66% <100.00%> (-0.40%)` | :arrow_down: |
| [...la-2.12/org/scalasteward/mill/plugin/package.scala](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org#diff-bW9kdWxlcy9taWxsLXBsdWdpbi9zcmMvbWFpbi9zY2FsYS0yLjEyL29yZy9zY2FsYXN0ZXdhcmQvbWlsbC9wbHVnaW4vcGFja2FnZS5zY2FsYQ==) | | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org). Last update [02d124b...6282c4e](https://codecov.io/gh/scala-steward-org/scala-steward/pull/2351?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org).


Patch:
diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala
index 063e2fbb..97d6b0ed 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala
@@ -95,7 +95,7 @@ final class StewardAlg[F[_]](config: Config)(implicit
       for {
         _ <- selfCheckAlg.checkAll
         _ <- workspaceAlg.cleanWorkspace
-        exitCode <- sbtAlg.addGlobalPlugins {
+        exitCode <- sbtAlg.addGlobalPlugins.surround {
           (config.githubApp.map(getGitHubAppRepos).getOrElse(Stream.empty) ++
             readRepos(config.reposFile))
             .evalMap(steward)
diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/maven/MavenAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/maven/MavenAlg.scala
index 8fb06733..e9728b8d 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/buildtool/maven/MavenAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/maven/MavenAlg.scala
@@ -17,7 +17,7 @@
 package org.scalasteward.core.buildtool.maven
 
 import better.files.File
-import cats.Monad
+import cats.effect.{MonadCancelThrow, Resource}
 import cats.syntax.all._
 import org.scalasteward.core.application.Config
 import org.scalasteward.core.buildtool.BuildToolAlg
@@ -34,7 +34,7 @@ object MavenAlg {
       fileAlg: FileAlg[F],
       processAlg: ProcessAlg[F],
       workspaceAlg: WorkspaceAlg[F],
-      F: Monad[F]
+      F: MonadCancelThrow[F]
   ): MavenAlg[F] =
     new MavenAlg[F] {
       override def containsBuild(buildRoot: BuildRoot): F[Boolean] =
@@ -61,15 +61,15 @@ object MavenAlg {
         F.unit
 
       def exec(command: Nel[String], repoDir: File): F[List[String]] =
-        maybeIgnoreOptsFiles(repoDir)(processAlg.execSandboxed(command, repoDir))
+        maybeIgnoreOptsFiles(repoDir).surround(processAlg.execSandboxed(command, repoDir))
 
       def mvnCmd(commands: String*): Nel[String] =
         Nel("mvn", "--batch-mode" :: commands.toList)
 
-      def maybeIgnoreOptsFiles[A](dir: File)(fa: F[A]): F[A] =
-        if (config.ignoreOptsFiles) ignoreOptsFiles(dir)(fa) else fa
+      def maybeIgnoreOptsFiles(dir: File): Resource[F, Unit] =
+        if (config.ignoreOptsFiles) ignoreOptsFiles(dir) else Resource.unit[F]
 
-      def ignoreOptsFiles[A](dir: File)(fa: F[A]): F[A] =
-        fileAlg.removeTemporarily(dir / ".jvmopts")(fa)
+      def ignoreOptsFiles(dir: File): Resource[F, Unit] =
+        fileAlg.removeTemporarily(dir / ".jvmopts")
     }
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/mill/MillAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/mill/MillAlg.scala
index 3957ca6f..28d39dbb 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/buildtool/mill/MillAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/mill/MillAlg.scala
@@ -58,7 +58,7 @@ object MillAlg {
         for {
           buildRootDir <- workspaceAlg.buildRootDir(buildRoot)
           predef = buildRootDir / "scala-steward.sc"
-          extracted <- fileAlg.createTemporarily(predef, content) {
+          extracted <- fileAlg.createTemporarily(predef, content).surround {
             val command = Nel("mill", List("-i", "-p", predef.toString, "show", extractDeps))
             processAlg.execSandboxed(command, buildRootDir)
           }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/sbt/SbtAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/sbt/SbtAlg.scala
index d0c9775b..2ee092c5 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/buildtool/sbt/SbtAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/sbt/SbtAlg.scala
@@ -19,7 +19,7 @@ package org.scalasteward.core.buildtool.sbt
 import better.files.File
 import cats.Functor
 import cats.data.OptionT
-import cats.effect.Concurrent
+import cats.effect.{Concurrent, Resource}
 import cats.syntax.all._
 import org.scalasteward.core.application.Config
 import org.scalasteward.core.buildtool.BuildToolAlg
@@ -33,9 +33,9 @@ import org.scalasteward.core.vcs.data.BuildRoot
 import org.typelevel.log4cats.Logger
 
 trait SbtAlg[F[_]] extends BuildToolAlg[F] {
-  def addGlobalPluginTemporarily[A](plugin: FileData)(fa: F[A]): F[A]
+  def addGlobalPluginTemporarily(plugin: FileData): Resource[F, Unit]
 
-  def addGlobalPlugins[A](fa: F[A]): F[A]
+  def addGlobalPlugins: Resource[F, Unit]
 
   def getSbtVersion(buildRoot: BuildRoot): F[Option[SbtVersion]]
 
@@ -53,19 +53,16 @@ object SbtAlg {
       F: Concurrent[F]
   ): SbtAlg[F] =
     new SbtAlg[F] {
-      override def addGlobalPluginTemporarily[A](plugin: FileData)(fa: F[A]): F[A] =
-        sbtDir.flatMap { dir =>
-          val plugins = "plugins"
-          fileAlg.createTemporarily(dir / "0.13" / plugins / plugin.name, plugin.content) {
-            fileAlg.createTemporarily(dir / "1.0" / plugins / plugin.name, plugin.content) {
-              fa
-            }
+      override def addGlobalPluginTemporarily(plugin: FileData): Resource[F, Unit] =
+        Resource.eval(sbtDir).flatMap { dir =>
+          List("0.13", "1.0").traverse_ { version =>
+            fileAlg.createTemporarily(dir / version / "plugins" / plugin.name, plugin.content)
           }
         }
 
-      override def addGlobalPlugins[A](fa: F[A]): F[A] =
-        logger.info("Add global sbt plugins") >>
-          stewardPlugin.flatMap(addGlobalPluginTemporarily(_)(fa))
+      override def addGlobalPlugins: Resource[F, Unit] =
+        Resource.eval(logger.info("Add global sbt plugins")) >>
+          Resource.eval(stewardPlugin).flatMap(addGlobalPluginTemporarily)
 
       override def containsBuild(buildRoot: BuildRoot): F[Boolean] =
         workspaceAlg
@@ -95,15 +92,15 @@ object SbtAlg {
         }
 
       private def runSourcesMigration(buildRoot: BuildRoot, migration: ScalafixMigration): F[Unit] =
-        addGlobalPluginTemporarily(scalaStewardScalafixSbt) {
+        addGlobalPluginTemporarily(scalaStewardScalafixSbt).surround {
           workspaceAlg.buildRootDir(buildRoot).flatMap { buildRootDir =>
             val withScalacOptions =
-              migration.scalacOptions.fold[F[Unit] => F[Unit]](identity) { opts =>
+              migration.scalacOptions.fold(Resource.unit[F]) { opts =>
                 val file = scalaStewardScalafixOptions(opts.toList)
-                fileAlg.createTemporarily(buildRootDir / file.name, file.content)(_)
+                fileAlg.createTemporarily(buildRootDir / file.name, file.content)
               }
             val scalafixCmds = migration.rewriteRules.map(rule => s"$scalafixAll $rule").toList
-            withScalacOptions(sbt(Nel(scalafixEnable, scalafixCmds), buildRootDir).void)
+            withScalacOptions.surround(sbt(Nel(scalafixEnable, scalafixCmds), buildRootDir).void)
           }
         }
 
@@ -124,7 +121,7 @@ object SbtAlg {
         fileAlg.home.map(_ / ".sbt")
 
       def sbt(sbtCommands: Nel[String], repoDir: File): F[List[String]] =
-        maybeIgnoreOptsFiles(repoDir) {
+        maybeIgnoreOptsFiles(repoDir).surround {
           val command =
             Nel.of(
               "sbt",
@@ -136,15 +133,11 @@ object SbtAlg {
           processAlg.execSandboxed(command, repoDir)
         }
 
-      def maybeIgnoreOptsFiles[A](dir: File)(fa: F[A]): F[A] =
-        if (config.ignoreOptsFiles) ignoreOptsFiles(dir)(fa) else fa
+      def maybeIgnoreOptsFiles[A](dir: File): Resource[F, Unit] =
+        if (config.ignoreOptsFiles) ignoreOptsFiles(dir) else Resource.unit[F]
 
-      def ignoreOptsFiles[A](dir: File)(fa: F[A]): F[A] =
-        fileAlg.removeTemporarily(dir / ".jvmopts") {
-          fileAlg.removeTemporarily(dir / ".sbtopts") {
-            fa
-          }
-        }
+      def ignoreOptsFiles(dir: File): Resource[F, Unit] =
+        List(".jvmopts", ".sbtopts").traverse_(file => fileAlg.removeTemporarily(dir / file))
 
       def getAdditionalDependencies(buildRoot: BuildRoot): F[List[Scope.Dependencies]] =
         getSbtDependency(buildRoot)
diff --git a/modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala
index 1929381b..9834be17 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala
@@ -17,9 +17,9 @@
 package org.scalasteward.core.io
 
 import better.files.File
-import cats.effect.{Concurrent, MonadCancel, Resource, Sync}
+import cats.effect.{Concurrent, Resource, Sync}
 import cats.syntax.all._
-import cats.{MonadThrow, Traverse}
+import cats.{ApplicativeError, MonadThrow, Traverse}
 import fs2.Stream
 import org.apache.commons.io.FileUtils
 import org.http4s.Uri
@@ -38,7 +38,7 @@ trait FileAlg[F[_]] {
 
   def isRegularFile(file: File): F[Boolean]
 
-  def removeTemporarily[A](file: File)(fa: F[A]): F[A]
+  def removeTemporarily(file: File): Resource[F, Unit]
 
   def readFile(file: File): F[Option[String]]
 
@@ -50,12 +50,12 @@ trait FileAlg[F[_]] {
 
   def writeFile(file: File, content: String): F[Unit]
 
-  final def createTemporarily[A, E](file: File, content: String)(
-      fa: F[A]
-  )(implicit F: MonadCancel[F, E]): F[A] = {
+  final def createTemporarily[E](file: File, content: String)(implicit
+      F: ApplicativeError[F, E]
+  ): Resource[F, Unit] = {
     val delete = deleteForce(file)
     val create = writeFile(file, content).onError(_ => delete)
-    F.bracket(create)(_ => fa)(_ => delete)
+    Resource.make(create)(_ => delete)
   }
 
   final def editFile(file: File, edit: String => Option[String])(implicit
@@ -111,16 +111,16 @@ object FileAlg {
       override def isRegularFile(file: File): F[Boolean] =
         F.blocking(file.isRegularFile(File.LinkOptions.noFollow))
 
-      override def removeTemporarily[A](file: File)(fa: F[A]): F[A] =
-        F.bracket {
+      override def removeTemporarily(file: File): Resource[F, Unit] =
+        Resource.make {
           F.blocking {
             val copyOptions = File.CopyOptions(overwrite = true)
             if (file.exists) Some(file.moveTo(File.newTemporaryFile())(copyOptions)) else None
           }
-        }(_ => fa) {
+        } {
           case Some(tmpFile) => F.blocking(tmpFile.moveTo(file)).void
           case None          => F.unit
-        }
+        }.void
 
       override def readFile(file: File): F[Option[String]] =
         F.blocking(if (file.exists) Some(file.contentAsString) else None)


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/buildtool/sbt/SbtAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/buildtool/sbt/SbtAlgTest.scala
index fe9f10be..1dde3de1 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/buildtool/sbt/SbtAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/buildtool/sbt/SbtAlgTest.scala
@@ -15,8 +15,8 @@ import org.scalasteward.core.vcs.data.{BuildRoot, Repo}
 
 class SbtAlgTest extends FunSuite {
   test("addGlobalPlugins") {
-    val obtained = sbtAlg
-      .addGlobalPlugins(Kleisli(_.update(_.exec(List("fa")))))
+    val obtained = sbtAlg.addGlobalPlugins
+      .surround(Kleisli(_.update(_.exec(List("fa")))))
       .runS(MockState.empty)
       .unsafeRunSync()
     val expected = MockState.empty.copy(
diff --git a/modules/core/src/test/scala/org/scalasteward/core/io/FileAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/io/FileAlgTest.scala
index 4de780ac..7f955af0 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/io/FileAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/io/FileAlgTest.scala
@@ -20,7 +20,7 @@ class FileAlgTest extends FunSuite {
 
     val p = for {
       before <- ioFileAlg.readFile(file)
-      during <- ioFileAlg.createTemporarily(file, content)(ioFileAlg.readFile(file))
+      during <- ioFileAlg.createTemporarily(file, content).surround(ioFileAlg.readFile(file))
       after <- ioFileAlg.readFile(file)
     } yield (before, during, after)
 
@@ -43,7 +43,7 @@ class FileAlgTest extends FunSuite {
     val p = for {
       _ <- ioFileAlg.writeFile(file, content)
       before <- ioFileAlg.readFile(file)
-      during <- ioFileAlg.removeTemporarily(file)(ioFileAlg.readFile(file))
+      during <- ioFileAlg.removeTemporarily(file).surround(ioFileAlg.readFile(file))
       after <- ioFileAlg.readFile(file)
     } yield (before, during, after)
 
@@ -52,7 +52,7 @@ class FileAlgTest extends FunSuite {
 
   test("removeTemporarily: nonexistent file") {
     val file = mockRoot / "does-not-exists.txt"
-    assertEquals(ioFileAlg.removeTemporarily(file)(IO.pure(42)).unsafeRunSync(), 42)
+    assertEquals(ioFileAlg.removeTemporarily(file).surround(IO.pure(42)).unsafeRunSync(), 42)
   }
 
   test("editFile: nonexistent file") {
diff --git a/modules/core/src/test/scala/org/scalasteward/core/io/MockFileAlg.scala b/modules/core/src/test/scala/org/scalasteward/core/io/MockFileAlg.scala
index 5ee0f3ce..f4567550 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/io/MockFileAlg.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/io/MockFileAlg.scala
@@ -2,11 +2,11 @@ package org.scalasteward.core.io
 
 import better.files.File
 import cats.data.Kleisli
-import cats.effect.IO
+import cats.effect.{IO, Resource}
 import fs2.Stream
 import org.http4s.Uri
 import org.scalasteward.core.io.FileAlgTest.ioFileAlg
-import org.scalasteward.core.mock.{getFlatMapSet, MockConfig, MockEff}
+import org.scalasteward.core.mock._
 
 class MockFileAlg extends FileAlg[MockEff] {
   override def deleteForce(file: File): MockEff[Unit] =
@@ -26,14 +26,12 @@ class MockFileAlg extends FileAlg[MockEff] {
       _.update(_.exec(List("test", "-f", file.pathAsString))) >> ioFileAlg.isRegularFile(file)
     }
 
-  override def removeTemporarily[A](file: File)(fa: MockEff[A]): MockEff[A] =
-    Kleisli { ref =>
-      for {
-        _ <- ref.update(_.exec(List("rm", file.pathAsString)))
-        a <- ioFileAlg.removeTemporarily(file)(fa.run(ref))
-        _ <- ref.update(_.exec(List("restore", file.pathAsString)))
-      } yield a
-    }
+  override def removeTemporarily(file: File): Resource[MockEff, Unit] =
+    for {
+      _ <- Resource.eval(Kleisli((_: MockCtx).update(_.exec(List("rm", file.pathAsString)))))
+      _ <- ioFileAlg.removeTemporarily(file).mapK(ioToMockEff)
+      _ <- Resource.eval(Kleisli((_: MockCtx).update(_.exec(List("restore", file.pathAsString)))))
+    } yield ()
 
   override def readFile(file: File): MockEff[Option[String]] =
     Kleisli(_.update(_.exec(List("read", file.pathAsString))) >> ioFileAlg.readFile(file))
diff --git a/modules/core/src/test/scala/org/scalasteward/core/mock/package.scala b/modules/core/src/test/scala/org/scalasteward/core/mock/package.scala
index 8083de95..94e8acd3 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/mock/package.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/mock/package.scala
@@ -1,14 +1,19 @@
 package org.scalasteward.core
 
-import cats.FlatMap
 import cats.data.Kleisli
 import cats.effect.{Async, IO, Ref}
 import cats.syntax.all._
+import cats.{~>, FlatMap}
 
 package object mock {
-  type MockEff[A] = Kleisli[IO, Ref[IO, MockState], A]
+  type MockCtx = Ref[IO, MockState]
+  type MockEff[A] = Kleisli[IO, MockCtx, A]
   val MockEff: Async[MockEff] = Async[MockEff]
 
+  val ioToMockEff: ~>[IO, MockEff] = new ~>[IO, MockEff] {
+    override def apply[A](fa: IO[A]): MockEff[A] = Kleisli(_ => fa)
+  }
+
   implicit class MockEffOps[A](private val fa: MockEff[A]) extends AnyVal {
     def runA(state: MockState): IO[A] =
       state.toRef.flatMap(fa.run)