From 89a66a8451399b272c66c206dce822feabd08df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Monniot?= Date: Sat, 9 Dec 2023 15:50:07 -0800 Subject: [PATCH] Implementation & test Also follow the same ScalaMocks pattern as the core library --- build.sbt | 9 +++- .../cats/{package.scala => ScalaMocks.scala} | 49 +++++++++++++------ .../monniot/scala3mock/cats/CatsSuite.scala | 21 ++++++++ 3 files changed, 63 insertions(+), 16 deletions(-) rename cats/src/main/scala/eu/monniot/scala3mock/cats/{package.scala => ScalaMocks.scala} (52%) create mode 100644 cats/src/test/scala/eu/monniot/scala3mock/cats/CatsSuite.scala diff --git a/build.sbt b/build.sbt index e897edb..ffcb69e 100644 --- a/build.sbt +++ b/build.sbt @@ -47,8 +47,13 @@ lazy val cats = project .in(file("./cats")) .dependsOn(core) .settings( - name := "scala3mock-scalatest", - libraryDependencies += "org.typelevel" %% "cats-core" % "2.9.0" + name := "scala3mock-cats", + libraryDependencies += "org.typelevel" %% "cats-core" % "2.9.0", + + libraryDependencies ++= Seq( + "org.scalameta" %% "munit" % "1.0.0-M10" % Test, + "org.typelevel" %% "cats-effect" % "3.5.2" % Test + ) ) lazy val scalatest = project diff --git a/cats/src/main/scala/eu/monniot/scala3mock/cats/package.scala b/cats/src/main/scala/eu/monniot/scala3mock/cats/ScalaMocks.scala similarity index 52% rename from cats/src/main/scala/eu/monniot/scala3mock/cats/package.scala rename to cats/src/main/scala/eu/monniot/scala3mock/cats/ScalaMocks.scala index 4b92a96..4a9ee1f 100644 --- a/cats/src/main/scala/eu/monniot/scala3mock/cats/package.scala +++ b/cats/src/main/scala/eu/monniot/scala3mock/cats/ScalaMocks.scala @@ -4,26 +4,45 @@ import cats.MonadError import eu.monniot.scala3mock.context.{Call, MockContext} import eu.monniot.scala3mock.functions.MockFunction1 import eu.monniot.scala3mock.handlers.{CallHandler, Handler, UnorderedHandlers} -import eu.monniot.scala3mock.main.TestExpectationEx +import eu.monniot.scala3mock.MockExpectationFailed import scala.annotation.unused import scala.collection.mutable.ListBuffer import scala.util.control.NonFatal + +object ScalaMocks extends ScalaMocks + +/** Helper trait that provide access to all components (mandatory or optional) + * used by the library to build mocks. + */ +trait ScalaMocks + extends eu.monniot.scala3mock.functions.MockFunctions + with eu.monniot.scala3mock.macros.Mocks + with eu.monniot.scala3mock.matchers.Matchers: + + // apparently using export in 3.2.2 lose the default value of the + // parameter. That might have been fixed in 3.3+, but we can't use + // that version so for now we will duplicate the definition. + def withExpectations[F[_], A](verifyAfterRun: Boolean = true)( + f: MockContext ?=> F[A] +)(using MonadError[F, Throwable]): F[A] = eu.monniot.scala3mock.cats.withExpectations(verifyAfterRun)(f) + + // A standalone function to run a test with a mock context, asserting all expectations at the end. def withExpectations[F[_], A](verifyAfterRun: Boolean = true)( f: MockContext ?=> F[A] )(using MonadError[F, Throwable]): F[A] = val ctx = new MockContext: - override type ExpectationException = TestExpectationEx + override type ExpectationException = MockExpectationFailed override def newExpectationException( message: String, methodName: Option[String] ): ExpectationException = - new TestExpectationEx(message, methodName) + new MockExpectationFailed(message, methodName) override def toString() = s"MockContext(callLog = $callLog)" @@ -42,14 +61,16 @@ def withExpectations[F[_], A](verifyAfterRun: Boolean = true)( if !oldExpectationContext.isSatisfied then ctx.reportUnsatisfiedExpectation(oldCallLog, oldExpectationContext) - try - initializeExpectations() - MonadError[F, Throwable] // TODO Wire the result and verification correctly - val result = f(using ctx) - if verifyAfterRun then verifyExpectations() - result - catch - case NonFatal(ex) => - // do not verify expectations - just clear them. Throw original exception - // see issue #72 - throw ex + initializeExpectations() + val me = MonadError[F, Throwable] + + me.flatMap(f(using ctx)) { a => + if verifyAfterRun then + try + verifyExpectations() + me.pure(a) + catch + case t => me.raiseError(t) + else + me.pure(a) + } diff --git a/cats/src/test/scala/eu/monniot/scala3mock/cats/CatsSuite.scala b/cats/src/test/scala/eu/monniot/scala3mock/cats/CatsSuite.scala new file mode 100644 index 0000000..bcc9e4c --- /dev/null +++ b/cats/src/test/scala/eu/monniot/scala3mock/cats/CatsSuite.scala @@ -0,0 +1,21 @@ +package eu.monniot.scala3mock.cats + +import cats.effect.SyncIO + +class CatsSuite extends munit.FunSuite with ScalaMocks { + + test("it should validate expectations after a lazy data type evaluated") { + // An implicit assumption of this code block is that the expectations + // are not validated on exit of the `withExpectations` function but when + // the returned Monad is being evaluated. + val fa = withExpectations() { + val intToStringMock = mockFunction[Int, String] + intToStringMock.expects(*) + + SyncIO(intToStringMock(2)) + } + + assertEquals(fa.unsafeRunSync(), null) + } + +}