├── .git-blame-ignore-revs ├── .github └── workflows │ ├── checks.yml │ └── release.yml ├── .gitignore ├── .scala-steward.conf ├── .scalafmt.conf ├── LICENSE ├── README.md ├── build.sbt ├── cats └── src │ ├── main │ └── scala │ │ └── eu │ │ └── monniot │ │ └── scala3mock │ │ └── cats │ │ └── ScalaMocks.scala │ └── test │ └── scala │ └── eu │ └── monniot │ └── scala3mock │ └── cats │ └── CatsSuite.scala ├── core └── src │ ├── main │ └── scala │ │ └── eu │ │ └── monniot │ │ └── scala3mock │ │ ├── Default.scala │ │ ├── MockExpectationFailed.scala │ │ ├── ScalaMocks.scala │ │ ├── context │ │ ├── Call.scala │ │ ├── Mock.scala │ │ └── MockContext.scala │ │ ├── functions │ │ ├── FakeFunction.scala │ │ ├── FunctionAdapters.scala │ │ ├── MockFunction.scala │ │ └── MockFunctions.scala │ │ ├── handlers │ │ ├── CallHandler.scala │ │ ├── CallHandlers.scala │ │ ├── Handler.scala │ │ ├── Handlers.scala │ │ └── UnorderedHandlers.scala │ │ ├── macros │ │ ├── MockImpl.scala │ │ ├── Mocks.scala │ │ ├── PrintAst.scala │ │ ├── WhenImpl.scala │ │ └── utils.scala │ │ ├── matchers │ │ ├── ArgumentMatcher.scala │ │ ├── MatchAny.scala │ │ ├── MatchEpsilon.scala │ │ ├── MatchPredicate.scala │ │ ├── MatcherBase.scala │ │ └── Matchers.scala │ │ └── withExpectations.scala │ └── test │ └── scala │ ├── eu │ └── monniot │ │ └── scala3mock │ │ ├── features │ │ ├── CallCountSuite.scala │ │ ├── MatchersSuite.scala │ │ ├── ReturnSuite.scala │ │ └── ThrowSuite.scala │ │ └── mock │ │ └── MockSuite.scala │ └── fixtures │ ├── ClassWithoutTypeParameters.scala │ ├── ContextBound.scala │ ├── ContextBoundInheritance.scala │ ├── HigherOrderPolymorphicTrait.scala │ ├── ManyParamsClass.scala │ ├── ManyParamsTrait.scala │ ├── PolymorphicClassWithParameters.scala │ ├── PolymorphicTrait.scala │ ├── SpecializedClass.scala │ ├── SpecializedClass2.scala │ ├── TestClass.scala │ ├── TestDefaultParameters.scala │ ├── TestTrait.scala │ ├── UserDatabase.scala │ ├── some │ └── other │ │ └── pkg │ │ └── SomeClass.scala │ └── yet │ └── another │ └── pkg │ └── YetAnotherClass.scala ├── docs ├── getting-started.md └── user-guide │ ├── advanced-topics.md │ ├── cats.md │ ├── faq.md │ ├── features.md │ ├── matching.md │ └── scalatest.md ├── integration └── src │ └── test ├── project ├── build.properties └── plugins.sbt ├── scalatest └── src │ ├── main │ └── scala │ │ └── eu │ │ └── monniot │ │ └── scala3mock │ │ └── scalatest │ │ ├── AsyncMockFactory.scala │ │ ├── BaseFactory.scala │ │ └── MockFactory.scala │ └── test │ └── scala │ └── eu │ └── monniot │ └── scala3mock │ ├── mockable │ └── TestTrait.scala │ └── scalatest │ ├── BasicAsyncTests.scala │ ├── BasicTests.scala │ ├── ErrorReportingTest.scala │ ├── FixtureContextTest.scala │ ├── SuiteScopeMockTest.scala │ ├── SuiteScopePresetMockParallelTest.scala │ ├── TestSuiteRunner.scala │ └── WithFixtureTest.scala └── website ├── core └── Footer.js ├── package.json ├── pages └── en │ └── index.js ├── sidebars.json ├── siteConfig.js └── static └── img ├── favicon.png └── logo.svg /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Scala Steward: Reformat with scalafmt 3.7.17 2 | d1f2a4f8d32f5706b23f7d85cc40712cbf4453a6 3 | 4 | # Scala Steward: Reformat with scalafmt 3.8.2 5 | 18cd2d2a33848f818916bff615ed2a7ccfa62156 6 | 7 | # Scala Steward: Reformat with scalafmt 3.8.3 8 | 57615e491e61d9996b7afb6b3d8707063a12c08b 9 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: Checks 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | scalafmt: 9 | name: Scalafmt 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-java@v4 14 | with: 15 | distribution: 'temurin' 16 | java-version: '11' 17 | - uses: sbt/setup-sbt@v1 18 | - run: sbt scalafmtCheckAll 19 | docs: 20 | name: Website 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | with: 25 | fetch-depth: 0 26 | - uses: actions/setup-java@v4 27 | with: 28 | distribution: 'temurin' 29 | java-version: '11' 30 | - uses: sbt/setup-sbt@v1 31 | - run: sbt 'docs/mdoc' 32 | # Note that the first Scala LTS 3.3 is unfortunately not compatible 33 | # So we consider 3.2.2 as our baseline until we get a LTS we 34 | # can target. 35 | test-base: 36 | name: Test (3.2.2) 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v3 40 | - uses: actions/setup-java@v4 41 | with: 42 | distribution: 'temurin' 43 | java-version: '11' 44 | - uses: sbt/setup-sbt@v1 45 | - run: sbt test 46 | # We still want to be able to test out the library on newer Scala version. 47 | # For those version, we compile the library using 3.2 and then run the tests 48 | # against the newer version. This effectively emulate what our users are doing. 49 | test-integration: 50 | name: Integration Test (${{ matrix.scalaV }}) 51 | runs-on: ubuntu-latest 52 | strategy: 53 | fail-fast: false 54 | matrix: 55 | scalaV: 56 | - "3.3.3" 57 | - "3.4.2" 58 | - "3.5.0-RC7" 59 | steps: 60 | - uses: actions/checkout@v3 61 | - uses: actions/setup-java@v4 62 | with: 63 | distribution: 'temurin' 64 | java-version: '11' 65 | - uses: sbt/setup-sbt@v1 66 | - run: | 67 | sbt 'core/publishLocal' 68 | sbt '++${{ matrix.scalaV }}! integration/test' 69 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: [main] 5 | tags: ["v*"] 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | with: 12 | fetch-depth: 0 13 | - uses: actions/setup-java@v4 14 | with: 15 | distribution: 'temurin' 16 | java-version: '11' 17 | - uses: sbt/setup-sbt@v1 18 | # - uses: olafurpg/setup-gpg@v3 19 | - name: Publish ${{ github.ref }} 20 | run: sbt ci-release 21 | env: 22 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 23 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 24 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 25 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 26 | - name: Check git diff 27 | if: ${{ failure() }} 28 | run: git diff 29 | - name: Print published version 30 | if: github.ref == 'refs/heads/main' 31 | run: | 32 | VERSION=$(sbt -no-colors 'inspect version' | grep "Setting: java.lang.String" | cut -d '=' -f2 | tr -d ' ') 33 | echo '### Snapshot version' >> $GITHUB_STEP_SUMMARY 34 | echo "version: $VERSION" >> $GITHUB_STEP_SUMMARY 35 | 36 | website: 37 | runs-on: ubuntu-latest 38 | needs: publish 39 | # We only publish the website on release 40 | if: startsWith(github.event.ref, 'refs/tags/v') 41 | steps: 42 | - uses: actions/checkout@v4 43 | with: 44 | fetch-depth: 0 45 | - uses: actions/setup-java@v4 46 | with: 47 | distribution: 'temurin' 48 | java-version: '11' 49 | cache: 'sbt' 50 | - uses: sbt/setup-sbt@v1 51 | - run: sbt 'docs/docusaurusPublishGhpages' 52 | env: 53 | GIT_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} 54 | - name: Check git diff 55 | if: ${{ failure() }} 56 | run: git diff 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # sbt specific 5 | dist/* 6 | target/ 7 | lib_managed/ 8 | src_managed/ 9 | project/boot/ 10 | project/plugins/project/ 11 | project/local-plugins.sbt 12 | .history 13 | .ensime 14 | .ensime_cache/ 15 | .sbt-scripted/ 16 | local.sbt 17 | 18 | # Bloop 19 | .bsp 20 | 21 | # VS Code 22 | .vscode/ 23 | 24 | # Metals 25 | .bloop/ 26 | .metals/ 27 | metals.sbt 28 | 29 | # IDEA 30 | .idea 31 | .idea_modules 32 | /.worksheet/ 33 | 34 | # website 35 | *.afdesign 36 | npm_dependencies/ 37 | website/node_modules 38 | website/build 39 | website/yarn.lock 40 | website/i18n 41 | -------------------------------------------------------------------------------- /.scala-steward.conf: -------------------------------------------------------------------------------- 1 | updates.pin = [ 2 | # Pinning the dependencies that require 3.3+ to their last known version. 3 | # This is because the library rely on an experimental API (to create new classes) 4 | # and experimental APIs on 3.3+ now either require a non-stable compiler version 5 | # or to pass the `-experimental` config parameter. 6 | # This was discovered in https://github.com/fmonniot/scala3mock/pull/50 7 | { groupId = "org.scala-lang", artifactId="scala3-library", version = "3.2." }, 8 | { groupId = "org.typelevel", artifactId="cats-core", version = "2.9." }, 9 | { groupId = "org.scalameta", artifactId="munit", version = "1.0.0-M11" } 10 | ] 11 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "3.8.3" 2 | runner.dialect = scala3 3 | fileOverride { 4 | "glob:**/*.sbt" { 5 | runner.dialect = scala212 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Original work Copyright (c) 2011-2015 ScalaMock Contributors (https://github.com/paulbutcher/ScalaMock/graphs/contributors) 2 | Modified work Copyright (c) 2022-2023 Scala3Mock Contributors (https://github.com/fmonniot/scala3mock/graphs/contributors) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > Deprecation notice: [ScalaMock](https://github.com/ScalaMock/ScalaMock) has added support for Scala 3 since version 6 and as a result it is recommended to use it instead of this library. 2 | 3 | ## Scala3Mock 4 | 5 | Heads on to https://francois.monniot.eu/scala3mock for the documentation. 6 | 7 | ### Licenses 8 | 9 | Adapted from 10 | https://github.com/OlegIlyenko/scala-icon/blob/master/scala-icon.svg for the scala icon.
11 | Under Creative Commons Attribution 4.0 International License. 12 | 13 | Adapted from https://github.com/paulbutcher/ScalaMock/blob/master/LICENCE.
14 | Under MIT License. Scala3Mock would not exists without their work ! 15 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / scalaVersion := "3.2.2" 2 | ThisBuild / organization := "eu.monniot" 3 | ThisBuild / homepage := Some(url("https://github.com/fmonniot/scala3mock")) 4 | ThisBuild / licenses := Seq("MIT" -> url("https://opensource.org/licenses/MIT")) 5 | ThisBuild / releaseNotesURL := Some( 6 | url("https://github.com/fmonniot/scala3mock/releases") 7 | ) 8 | ThisBuild / scmInfo := Some( 9 | ScmInfo( 10 | url("https://github.com/fmonniot/scala3mock"), 11 | "git@github.com:fmonniot/scala3mock.git" 12 | ) 13 | ) 14 | ThisBuild / versionScheme := Some("early-semver") 15 | 16 | ThisBuild / developers := List( 17 | Developer( 18 | "fmonniot", 19 | "François Monniot", 20 | "francoismonniot@gmail.com", 21 | url("https://francois.monniot.eu") 22 | ) 23 | 24 | // Not sure if we should include the original ScalaMock devs here as well ? 25 | ) 26 | 27 | lazy val scala3mock = project 28 | .in(file(".")) 29 | .aggregate(core, cats, scalatest) 30 | .settings(publish / skip := true) 31 | 32 | lazy val core = project 33 | .in(file("./core")) 34 | .settings( 35 | name := "scala3mock", 36 | scalacOptions ++= Seq("-feature", "-explain"), 37 | Test / scalacOptions += "-Xcheck-macros", 38 | 39 | // Useful when using the PrintAst[type] macro. That will provides the implementation 40 | // details of classes. Without it, only the signatures are available. 41 | // Test / scalacOptions += "-Yretain-trees", // For debugging when writing macros 42 | 43 | libraryDependencies += "org.scalameta" %% "munit" % "1.0.0-M11" % Test 44 | ) 45 | 46 | lazy val cats = project 47 | .in(file("./cats")) 48 | .dependsOn(core) 49 | .settings( 50 | name := "scala3mock-cats", 51 | libraryDependencies += "org.typelevel" %% "cats-core" % "2.9.0", 52 | libraryDependencies ++= Seq( 53 | "org.scalameta" %% "munit" % "1.0.0-M11" % Test, 54 | "org.typelevel" %% "cats-effect" % "3.5.7" % Test 55 | ) 56 | ) 57 | 58 | lazy val scalatest = project 59 | .in(file("./scalatest")) 60 | .dependsOn(core) 61 | .settings( 62 | name := "scala3mock-scalatest", 63 | libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" 64 | ) 65 | 66 | lazy val docs = project 67 | .in(file("site-docs")) // important: it must not be docs/ 68 | .dependsOn(core, cats, scalatest) 69 | .enablePlugins(MdocPlugin, DocusaurusPlugin) 70 | .settings( 71 | publish / skip := true, 72 | mdocVariables := Map( 73 | "VERSION" -> (core / version).value 74 | ), 75 | libraryDependencies += "org.typelevel" %% "cats-effect" % "3.5.7" 76 | ) 77 | 78 | /* This project is a bit particular in how it operates. Because compiling the library itself 79 | is restricted to 3.2.x at the moment, we cannot use the regular cross compilation scheme for 80 | testing. Instead, what we do is that we compile the library using 3.2.2, and then depend on 81 | that compiled jar in the integration tests using more recent version of the compiler. 82 | 83 | This project is only used to provide this integration point (the src directory only contain 84 | a symlink to the core tests). See the GitHub Actions to see how to use it. 85 | 86 | */ 87 | lazy val integration = project 88 | .in(file("./integration")) 89 | .settings( 90 | name := "scala3mock-integration-tests", 91 | crossScalaVersions := Seq("3.3.3", "3.4.0", "3.5.0-RC1"), 92 | 93 | // Note that this means we need to publish core via publishLocal first. 94 | libraryDependencies += "eu.monniot" %% "scala3mock" % version.value % Test, 95 | libraryDependencies += "org.scalameta" %% "munit" % "1.0.0-M11" % Test 96 | ) 97 | -------------------------------------------------------------------------------- /cats/src/main/scala/eu/monniot/scala3mock/cats/ScalaMocks.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.cats 2 | 3 | import cats.MonadError 4 | import eu.monniot.scala3mock.context.{Call, MockContext} 5 | import eu.monniot.scala3mock.functions.MockFunction1 6 | import eu.monniot.scala3mock.handlers.{CallHandler, Handler, UnorderedHandlers} 7 | import eu.monniot.scala3mock.MockExpectationFailed 8 | 9 | import scala.annotation.unused 10 | import scala.collection.mutable.ListBuffer 11 | import scala.util.control.NonFatal 12 | 13 | object ScalaMocks extends ScalaMocks 14 | 15 | /** Helper trait that provide access to all components (mandatory or optional) 16 | * used by the library to build mocks. 17 | */ 18 | trait ScalaMocks 19 | extends eu.monniot.scala3mock.functions.MockFunctions 20 | with eu.monniot.scala3mock.macros.Mocks 21 | with eu.monniot.scala3mock.matchers.Matchers: 22 | 23 | // apparently using export in 3.2.2 lose the default value of the 24 | // parameter. That might have been fixed in 3.3+, but we can't use 25 | // that version so for now we will duplicate the definition. 26 | def withExpectations[F[_], A](verifyAfterRun: Boolean = true)( 27 | f: MockContext ?=> F[A] 28 | )(using MonadError[F, Throwable]): F[A] = 29 | eu.monniot.scala3mock.cats.withExpectations(verifyAfterRun)(f) 30 | 31 | // A standalone function to run a test with a mock context, asserting all expectations at the end. 32 | def withExpectations[F[_], A](verifyAfterRun: Boolean = true)( 33 | f: MockContext ?=> F[A] 34 | )(using MonadError[F, Throwable]): F[A] = 35 | 36 | val ctx = new MockContext: 37 | override type ExpectationException = MockExpectationFailed 38 | 39 | override def newExpectationException( 40 | message: String, 41 | methodName: Option[String] 42 | ): ExpectationException = 43 | new MockExpectationFailed(message, methodName) 44 | 45 | override def toString() = s"MockContext(callLog = $callLog)" 46 | 47 | def initializeExpectations(): Unit = 48 | val initialHandlers = new UnorderedHandlers 49 | 50 | ctx.callLog = new ListBuffer[Call] 51 | ctx.expectationContext = initialHandlers 52 | 53 | def verifyExpectations(): Unit = 54 | ctx.callLog foreach ctx.expectationContext.verify _ 55 | 56 | val oldCallLog = ctx.callLog 57 | val oldExpectationContext = ctx.expectationContext 58 | 59 | if !oldExpectationContext.isSatisfied then 60 | ctx.reportUnsatisfiedExpectation(oldCallLog, oldExpectationContext) 61 | 62 | initializeExpectations() 63 | val me = MonadError[F, Throwable] 64 | 65 | me.flatMap(f(using ctx)) { a => 66 | if verifyAfterRun then 67 | try 68 | verifyExpectations() 69 | me.pure(a) 70 | catch case t => me.raiseError(t) 71 | else me.pure(a) 72 | } 73 | -------------------------------------------------------------------------------- /cats/src/test/scala/eu/monniot/scala3mock/cats/CatsSuite.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.cats 2 | 3 | import cats.effect.SyncIO 4 | 5 | class CatsSuite extends munit.FunSuite with ScalaMocks { 6 | 7 | test("it should validate expectations after a lazy data type evaluated") { 8 | // An implicit assumption of this code block is that the expectations 9 | // are not validated on exit of the `withExpectations` function but when 10 | // the returned Monad is being evaluated. 11 | val fa = withExpectations() { 12 | val intToStringMock = mockFunction[Int, String] 13 | intToStringMock.expects(*) 14 | 15 | SyncIO(intToStringMock(2)) 16 | } 17 | 18 | assertEquals(fa.unsafeRunSync(), null) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/Default.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock 2 | 3 | /** Provides a default value for some type. This is used for mocked functions to 4 | * return something if no specific value have beeen provided. 5 | * 6 | * Note that `Any` doesn't have any default at the moment. If you decide to 7 | * implement `Default` for your own type, do note that the library's mock macro 8 | * currently doesn't support higher-kinded types and default to `null` for 9 | * those. If this is necessary for you, we welcome Pull Requests! 10 | */ 11 | trait Default[A]: 12 | val default: A 13 | 14 | object Default: 15 | 16 | def apply[A](using d: Default[A]): Default[A] = d 17 | 18 | // AnyVal 19 | given Default[Byte] with { val default: Byte = 0 } 20 | given Default[Short] with { val default: Short = 0 } 21 | given Default[Char] with { val default: Char = 0 } 22 | given Default[Int] with { val default = 0 } 23 | given Default[Long] with { val default = 0L } 24 | given Default[Float] with { val default = 0.0f } 25 | given Default[Double] with { val default = 0.0d } 26 | given Default[Boolean] with { val default = false } 27 | given Default[Unit] with { val default = () } 28 | 29 | // AnyRef 30 | given Default[java.io.OutputStream] with { 31 | val default = java.io.OutputStream.nullOutputStream() 32 | } 33 | given [Y]: Default[Y] with { val default = null.asInstanceOf[Y] } 34 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/MockExpectationFailed.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock 2 | 3 | import eu.monniot.scala3mock.context.{Call, MockContext} 4 | import eu.monniot.scala3mock.functions.MockFunction1 5 | import eu.monniot.scala3mock.handlers.{CallHandler, Handler, UnorderedHandlers} 6 | 7 | import scala.annotation.unused 8 | import scala.collection.mutable.ListBuffer 9 | import scala.util.control.NonFatal 10 | 11 | class MockExpectationFailed(message: String, methodName: Option[String]) 12 | extends Throwable: 13 | override def getMessage(): String = message 14 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/ScalaMocks.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock 2 | 3 | import eu.monniot.scala3mock.context.MockContext 4 | 5 | object ScalaMocks extends ScalaMocks 6 | 7 | /** Helper trait that provide access to all components (mandatory or optional) 8 | * used by the library to build mocks. 9 | */ 10 | trait ScalaMocks 11 | extends functions.MockFunctions 12 | with macros.Mocks 13 | with matchers.Matchers: 14 | 15 | // apparently using export in 3.2.2 lose the default value of the 16 | // parameter. That might have been fixed in 3.3+, but we can't use 17 | // that version so for now we will duplicate the definition. 18 | def withExpectations[A](verifyAfterRun: Boolean = true)( 19 | f: MockContext ?=> A 20 | ): A = eu.monniot.scala3mock.withExpectations(verifyAfterRun)(f) 21 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/context/Call.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.context 2 | 3 | import eu.monniot.scala3mock.functions.FakeFunction 4 | 5 | case class Call(target: FakeFunction, arguments: Product): 6 | override def toString: String = 7 | s"$target${arguments.productIterator.mkString("(", ", ", ")")}" 8 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/context/Mock.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.context 2 | 3 | import eu.monniot.scala3mock.functions.MockFunction 4 | 5 | import scala.annotation.unused 6 | 7 | private[scala3mock] trait Mock: 8 | 9 | // This is a hack to go around my inability to call methods defined on companion objects 10 | @unused 11 | protected def createMap( 12 | elems: (String, MockFunction)* 13 | ): Map[String, MockFunction] = Map.from(elems) 14 | 15 | def accessMockFunction(name: String): MockFunction 16 | 17 | object Mock: 18 | case object ReceiverIsNotAMock 19 | extends Throwable( 20 | "The object being mocked is not mockable. Use the mock[] function to create a mock." 21 | ) 22 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/context/MockContext.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.context 2 | 3 | import eu.monniot.scala3mock.handlers.{CallHandler, Handlers, UnorderedHandlers} 4 | 5 | import scala.collection.mutable.ListBuffer 6 | 7 | trait MockContext: 8 | type ExpectationException <: Throwable 9 | def newExpectationException( 10 | message: String, 11 | methodName: Option[String] = None 12 | ): ExpectationException 13 | 14 | private[scala3mock] var callLog: ListBuffer[Call] = ListBuffer() 15 | private[scala3mock] var expectationContext: Handlers = UnorderedHandlers() 16 | // val mockNameGenerator: MockNameGenerator = new MockNameGenerator() 17 | 18 | def add[E <: CallHandler[_]](e: E): E = 19 | assert( 20 | expectationContext != null, 21 | "Null expectation context - missing withExpectations?" 22 | ) 23 | expectationContext.add(e) 24 | e 25 | 26 | def reportUnexpectedCall(call: Call) = 27 | throw newExpectationException( 28 | s"Unexpected call: $call\n\n${errorContext(callLog, expectationContext)}", 29 | Some(call.target.name) 30 | ) 31 | 32 | def reportUnsatisfiedExpectation( 33 | callLog: ListBuffer[Call], 34 | expectationContext: Handlers 35 | ) = 36 | throw newExpectationException( 37 | s"Unsatisfied expectation:\n\n${errorContext(callLog, expectationContext)}" 38 | ) 39 | 40 | def errorContext(callLog: ListBuffer[Call], expectationContext: Handlers) = 41 | s"Expected:\n$expectationContext\n\nActual:\n${callLog.mkString(" ", "\n ", "")}" 42 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/functions/FakeFunction.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.functions 2 | 3 | import eu.monniot.scala3mock.context.{Call, MockContext} 4 | 5 | // Base class. This hold the function state (mutable). 6 | trait FakeFunction(protected val mockContext: MockContext, val name: String): 7 | 8 | // [issue #25] we must always refer current mockContext vars because 9 | // they are updated during inititialize/resetExpectations! 10 | private def callLog = mockContext.callLog 11 | 12 | private def expectationContext = mockContext.expectationContext 13 | 14 | def handle(arguments: Product): Any = 15 | if callLog != null then 16 | val call = Call(this, arguments) 17 | callLog += call 18 | expectationContext.handle(call) getOrElse onUnexpected(call) 19 | else 20 | val msg = 21 | "Can't log call to mock object, have expectations been verified already?" 22 | throw new RuntimeException(msg) 23 | 24 | override def toString: String = scala.reflect.NameTransformer.decode(name) 25 | 26 | protected def onUnexpected(call: Call): Any 27 | 28 | // format: off 29 | abstract class FakeFunction0[R](mockContext: MockContext, name: String) 30 | extends (() => R) 31 | with FakeFunction(mockContext, name): 32 | 33 | def apply(): R = handle(None).asInstanceOf[R] 34 | 35 | abstract class FakeFunction1[T1, R](mockContext: MockContext, name: String) 36 | extends (T1 => R) 37 | with FakeFunction(mockContext, name): 38 | 39 | def apply(v1: T1): R = handle(Tuple1(v1)).asInstanceOf[R] 40 | 41 | abstract class FakeFunction2[T1, T2, R](mockContext: MockContext, name: String) 42 | extends ((T1, T2) => R) 43 | with FakeFunction(mockContext, name): 44 | 45 | def apply(v1: T1, v2: T2): R = handle((v1, v2)).asInstanceOf[R] 46 | 47 | abstract class FakeFunction3[T1, T2, T3, R]( 48 | mockContext: MockContext, 49 | name: String 50 | ) extends ((T1, T2, T3) => R) 51 | with FakeFunction(mockContext, name): 52 | 53 | def apply(v1: T1, v2: T2, v3: T3): R = handle((v1, v2, v3)).asInstanceOf[R] 54 | 55 | abstract class FakeFunction4[T1, T2, T3, T4, R]( 56 | mockContext: MockContext, 57 | name: String 58 | ) extends ((T1, T2, T3, T4) => R) 59 | with FakeFunction(mockContext, name): 60 | 61 | def apply(v1: T1, v2: T2, v3: T3, v4: T4): R = 62 | handle((v1, v2, v3, v4)).asInstanceOf[R] 63 | 64 | abstract class FakeFunction5[T1, T2, T3, T4, T5, R]( 65 | mockContext: MockContext, 66 | name: String 67 | ) extends ((T1, T2, T3, T4, T5) => R) 68 | with FakeFunction(mockContext, name): 69 | 70 | def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5): R = handle( 71 | (v1, v2, v3, v4, v5) 72 | ).asInstanceOf[R] 73 | 74 | abstract class FakeFunction6[T1, T2, T3, T4, T5, T6, R]( 75 | mockContext: MockContext, 76 | name: String 77 | ) extends ((T1, T2, T3, T4, T5, T6) => R) 78 | with FakeFunction(mockContext, name): 79 | 80 | def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6): R = handle( 81 | (v1, v2, v3, v4, v5, v6) 82 | ).asInstanceOf[R] 83 | 84 | abstract class FakeFunction7[T1, T2, T3, T4, T5, T6, T7, R]( 85 | mockContext: MockContext, 86 | name: String 87 | ) extends ((T1, T2, T3, T4, T5, T6, T7) => R) 88 | with FakeFunction(mockContext, name): 89 | 90 | def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7): R = handle( 91 | (v1, v2, v3, v4, v5, v6, v7) 92 | ).asInstanceOf[R] 93 | 94 | abstract class FakeFunction8[T1, T2, T3, T4, T5, T6, T7, T8, R]( 95 | mockContext: MockContext, 96 | name: String 97 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8) => R) 98 | with FakeFunction(mockContext, name): 99 | 100 | def apply(v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8): R = 101 | handle((v1, v2, v3, v4, v5, v6, v7, v8)).asInstanceOf[R] 102 | 103 | abstract class FakeFunction9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R]( 104 | mockContext: MockContext, 105 | name: String 106 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9) => R) 107 | with FakeFunction(mockContext, name): 108 | 109 | def apply( 110 | v1: T1, 111 | v2: T2, 112 | v3: T3, 113 | v4: T4, 114 | v5: T5, 115 | v6: T6, 116 | v7: T7, 117 | v8: T8, 118 | v9: T9 119 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9)).asInstanceOf[R] 120 | 121 | abstract class FakeFunction10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R]( 122 | mockContext: MockContext, 123 | name: String 124 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) => R) 125 | with FakeFunction(mockContext, name): 126 | 127 | def apply( 128 | v1: T1, 129 | v2: T2, 130 | v3: T3, 131 | v4: T4, 132 | v5: T5, 133 | v6: T6, 134 | v7: T7, 135 | v8: T8, 136 | v9: T9, 137 | v10: T10 138 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10)).asInstanceOf[R] 139 | 140 | abstract class FakeFunction11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R]( 141 | mockContext: MockContext, 142 | name: String 143 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) => R) 144 | with FakeFunction(mockContext, name): 145 | 146 | def apply( 147 | v1: T1, 148 | v2: T2, 149 | v3: T3, 150 | v4: T4, 151 | v5: T5, 152 | v6: T6, 153 | v7: T7, 154 | v8: T8, 155 | v9: T9, 156 | v10: T10, 157 | v11: T11, 158 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11)).asInstanceOf[R] 159 | 160 | abstract class FakeFunction12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R]( 161 | mockContext: MockContext, 162 | name: String 163 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) => R) 164 | with FakeFunction(mockContext, name): 165 | 166 | def apply( 167 | v1: T1, 168 | v2: T2, 169 | v3: T3, 170 | v4: T4, 171 | v5: T5, 172 | v6: T6, 173 | v7: T7, 174 | v8: T8, 175 | v9: T9, 176 | v10: T10, 177 | v11: T11, 178 | v12: T12, 179 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12)).asInstanceOf[R] 180 | 181 | abstract class FakeFunction13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R]( 182 | mockContext: MockContext, 183 | name: String 184 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) => R) 185 | with FakeFunction(mockContext, name): 186 | 187 | def apply( 188 | v1: T1, 189 | v2: T2, 190 | v3: T3, 191 | v4: T4, 192 | v5: T5, 193 | v6: T6, 194 | v7: T7, 195 | v8: T8, 196 | v9: T9, 197 | v10: T10, 198 | v11: T11, 199 | v12: T12, 200 | v13: T13, 201 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13)).asInstanceOf[R] 202 | 203 | abstract class FakeFunction14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R]( 204 | mockContext: MockContext, 205 | name: String 206 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) => R) 207 | with FakeFunction(mockContext, name): 208 | 209 | def apply( 210 | v1: T1, 211 | v2: T2, 212 | v3: T3, 213 | v4: T4, 214 | v5: T5, 215 | v6: T6, 216 | v7: T7, 217 | v8: T8, 218 | v9: T9, 219 | v10: T10, 220 | v11: T11, 221 | v12: T12, 222 | v13: T13, 223 | v14: T14, 224 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14)).asInstanceOf[R] 225 | 226 | abstract class FakeFunction15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R]( 227 | mockContext: MockContext, 228 | name: String 229 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) => R) 230 | with FakeFunction(mockContext, name): 231 | 232 | def apply( 233 | v1: T1, 234 | v2: T2, 235 | v3: T3, 236 | v4: T4, 237 | v5: T5, 238 | v6: T6, 239 | v7: T7, 240 | v8: T8, 241 | v9: T9, 242 | v10: T10, 243 | v11: T11, 244 | v12: T12, 245 | v13: T13, 246 | v14: T14, 247 | v15: T15, 248 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15)).asInstanceOf[R] 249 | 250 | abstract class FakeFunction16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R]( 251 | mockContext: MockContext, 252 | name: String 253 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) => R) 254 | with FakeFunction(mockContext, name): 255 | 256 | def apply( 257 | v1: T1, 258 | v2: T2, 259 | v3: T3, 260 | v4: T4, 261 | v5: T5, 262 | v6: T6, 263 | v7: T7, 264 | v8: T8, 265 | v9: T9, 266 | v10: T10, 267 | v11: T11, 268 | v12: T12, 269 | v13: T13, 270 | v14: T14, 271 | v15: T15, 272 | v16: T16, 273 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16)).asInstanceOf[R] 274 | 275 | abstract class FakeFunction17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R]( 276 | mockContext: MockContext, 277 | name: String 278 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) => R) 279 | with FakeFunction(mockContext, name): 280 | 281 | def apply( 282 | v1: T1, 283 | v2: T2, 284 | v3: T3, 285 | v4: T4, 286 | v5: T5, 287 | v6: T6, 288 | v7: T7, 289 | v8: T8, 290 | v9: T9, 291 | v10: T10, 292 | v11: T11, 293 | v12: T12, 294 | v13: T13, 295 | v14: T14, 296 | v15: T15, 297 | v16: T16, 298 | v17: T17, 299 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17)).asInstanceOf[R] 300 | 301 | abstract class FakeFunction18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R]( 302 | mockContext: MockContext, 303 | name: String 304 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) => R) 305 | with FakeFunction(mockContext, name): 306 | 307 | def apply( 308 | v1: T1, 309 | v2: T2, 310 | v3: T3, 311 | v4: T4, 312 | v5: T5, 313 | v6: T6, 314 | v7: T7, 315 | v8: T8, 316 | v9: T9, 317 | v10: T10, 318 | v11: T11, 319 | v12: T12, 320 | v13: T13, 321 | v14: T14, 322 | v15: T15, 323 | v16: T16, 324 | v17: T17, 325 | v18: T18, 326 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18)).asInstanceOf[R] 327 | 328 | abstract class FakeFunction19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R]( 329 | mockContext: MockContext, 330 | name: String 331 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) => R) 332 | with FakeFunction(mockContext, name): 333 | 334 | def apply( 335 | v1: T1, 336 | v2: T2, 337 | v3: T3, 338 | v4: T4, 339 | v5: T5, 340 | v6: T6, 341 | v7: T7, 342 | v8: T8, 343 | v9: T9, 344 | v10: T10, 345 | v11: T11, 346 | v12: T12, 347 | v13: T13, 348 | v14: T14, 349 | v15: T15, 350 | v16: T16, 351 | v17: T17, 352 | v18: T18, 353 | v19: T19, 354 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19)).asInstanceOf[R] 355 | 356 | abstract class FakeFunction20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R]( 357 | mockContext: MockContext, 358 | name: String 359 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20) => R) 360 | with FakeFunction(mockContext, name): 361 | 362 | def apply( 363 | v1: T1, 364 | v2: T2, 365 | v3: T3, 366 | v4: T4, 367 | v5: T5, 368 | v6: T6, 369 | v7: T7, 370 | v8: T8, 371 | v9: T9, 372 | v10: T10, 373 | v11: T11, 374 | v12: T12, 375 | v13: T13, 376 | v14: T14, 377 | v15: T15, 378 | v16: T16, 379 | v17: T17, 380 | v18: T18, 381 | v19: T19, 382 | v20: T20, 383 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20)).asInstanceOf[R] 384 | 385 | abstract class FakeFunction21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R]( 386 | mockContext: MockContext, 387 | name: String 388 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21) => R) 389 | with FakeFunction(mockContext, name): 390 | 391 | def apply( 392 | v1: T1, 393 | v2: T2, 394 | v3: T3, 395 | v4: T4, 396 | v5: T5, 397 | v6: T6, 398 | v7: T7, 399 | v8: T8, 400 | v9: T9, 401 | v10: T10, 402 | v11: T11, 403 | v12: T12, 404 | v13: T13, 405 | v14: T14, 406 | v15: T15, 407 | v16: T16, 408 | v17: T17, 409 | v18: T18, 410 | v19: T19, 411 | v20: T20, 412 | v21: T21, 413 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21)).asInstanceOf[R] 414 | 415 | abstract class FakeFunction22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R]( 416 | mockContext: MockContext, 417 | name: String 418 | ) extends ((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22) => R) 419 | with FakeFunction(mockContext, name): 420 | 421 | def apply( 422 | v1: T1, 423 | v2: T2, 424 | v3: T3, 425 | v4: T4, 426 | v5: T5, 427 | v6: T6, 428 | v7: T7, 429 | v8: T8, 430 | v9: T9, 431 | v10: T10, 432 | v11: T11, 433 | v12: T12, 434 | v13: T13, 435 | v14: T14, 436 | v15: T15, 437 | v16: T16, 438 | v17: T17, 439 | v18: T18, 440 | v19: T19, 441 | v20: T20, 442 | v21: T21, 443 | v22: T22, 444 | ): R = handle((v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22)).asInstanceOf[R] 445 | 446 | // format: on 447 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/functions/FunctionAdapters.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.functions 2 | 3 | // format: off 4 | class FunctionAdapter0[R](f: () => R) extends Function1[Product, R]: 5 | 6 | def apply(args: Product) = 7 | assert(args.productArity == 0) 8 | f() 9 | 10 | class FunctionAdapter1[T1, R](f: T1 => R) extends Function1[Product, R]: 11 | 12 | def apply(args: Product) = 13 | assert(args.productArity == 1) 14 | f(args.productElement(0).asInstanceOf[T1]) 15 | 16 | class FunctionAdapter2[T1, T2, R](f: (T1, T2) => R) 17 | extends Function1[Product, R]: 18 | 19 | def apply(args: Product) = 20 | assert(args.productArity == 2) 21 | f( 22 | args.productElement(0).asInstanceOf[T1], 23 | args.productElement(1).asInstanceOf[T2] 24 | ) 25 | 26 | class FunctionAdapter3[T1, T2, T3, R](f: (T1, T2, T3) => R) 27 | extends Function1[Product, R]: 28 | 29 | def apply(args: Product) = 30 | assert(args.productArity == 3) 31 | f( 32 | args.productElement(0).asInstanceOf[T1], 33 | args.productElement(1).asInstanceOf[T2], 34 | args.productElement(2).asInstanceOf[T3] 35 | ) 36 | 37 | class FunctionAdapter4[T1, T2, T3, T4, R](f: (T1, T2, T3, T4) => R) 38 | extends Function1[Product, R]: 39 | 40 | def apply(args: Product) = 41 | assert(args.productArity == 4) 42 | f( 43 | args.productElement(0).asInstanceOf[T1], 44 | args.productElement(1).asInstanceOf[T2], 45 | args.productElement(2).asInstanceOf[T3], 46 | args.productElement(3).asInstanceOf[T4] 47 | ) 48 | 49 | class FunctionAdapter5[T1, T2, T3, T4, T5, R](f: (T1, T2, T3, T4, T5) => R) 50 | extends Function1[Product, R]: 51 | 52 | def apply(args: Product) = 53 | assert(args.productArity == 5) 54 | f( 55 | args.productElement(0).asInstanceOf[T1], 56 | args.productElement(1).asInstanceOf[T2], 57 | args.productElement(2).asInstanceOf[T3], 58 | args.productElement(3).asInstanceOf[T4], 59 | args.productElement(4).asInstanceOf[T5] 60 | ) 61 | 62 | class FunctionAdapter6[T1, T2, T3, T4, T5, T6, R]( 63 | f: (T1, T2, T3, T4, T5, T6) => R 64 | ) extends Function1[Product, R]: 65 | 66 | def apply(args: Product) = 67 | assert(args.productArity == 6) 68 | f( 69 | args.productElement(0).asInstanceOf[T1], 70 | args.productElement(1).asInstanceOf[T2], 71 | args.productElement(2).asInstanceOf[T3], 72 | args.productElement(3).asInstanceOf[T4], 73 | args.productElement(4).asInstanceOf[T5], 74 | args.productElement(5).asInstanceOf[T6] 75 | ) 76 | 77 | class FunctionAdapter7[T1, T2, T3, T4, T5, T6, T7, R]( 78 | f: (T1, T2, T3, T4, T5, T6, T7) => R 79 | ) extends Function1[Product, R]: 80 | 81 | def apply(args: Product) = 82 | assert(args.productArity == 7) 83 | f( 84 | args.productElement(0).asInstanceOf[T1], 85 | args.productElement(1).asInstanceOf[T2], 86 | args.productElement(2).asInstanceOf[T3], 87 | args.productElement(3).asInstanceOf[T4], 88 | args.productElement(4).asInstanceOf[T5], 89 | args.productElement(5).asInstanceOf[T6], 90 | args.productElement(6).asInstanceOf[T7] 91 | ) 92 | 93 | class FunctionAdapter8[T1, T2, T3, T4, T5, T6, T7, T8, R]( 94 | f: (T1, T2, T3, T4, T5, T6, T7, T8) => R 95 | ) extends Function1[Product, R]: 96 | 97 | def apply(args: Product) = 98 | assert(args.productArity == 8) 99 | f( 100 | args.productElement(0).asInstanceOf[T1], 101 | args.productElement(1).asInstanceOf[T2], 102 | args.productElement(2).asInstanceOf[T3], 103 | args.productElement(3).asInstanceOf[T4], 104 | args.productElement(4).asInstanceOf[T5], 105 | args.productElement(5).asInstanceOf[T6], 106 | args.productElement(6).asInstanceOf[T7], 107 | args.productElement(7).asInstanceOf[T8] 108 | ) 109 | 110 | class FunctionAdapter9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R]( 111 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9) => R 112 | ) extends Function1[Product, R]: 113 | 114 | def apply(args: Product) = 115 | assert(args.productArity == 9) 116 | f( 117 | args.productElement(0).asInstanceOf[T1], 118 | args.productElement(1).asInstanceOf[T2], 119 | args.productElement(2).asInstanceOf[T3], 120 | args.productElement(3).asInstanceOf[T4], 121 | args.productElement(4).asInstanceOf[T5], 122 | args.productElement(5).asInstanceOf[T6], 123 | args.productElement(6).asInstanceOf[T7], 124 | args.productElement(7).asInstanceOf[T8], 125 | args.productElement(8).asInstanceOf[T9] 126 | ) 127 | 128 | class FunctionAdapter10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R]( 129 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) => R 130 | ) extends Function1[Product, R]: 131 | 132 | def apply(args: Product) = 133 | assert(args.productArity == 10) 134 | f( 135 | args.productElement(0).asInstanceOf[T1], 136 | args.productElement(1).asInstanceOf[T2], 137 | args.productElement(2).asInstanceOf[T3], 138 | args.productElement(3).asInstanceOf[T4], 139 | args.productElement(4).asInstanceOf[T5], 140 | args.productElement(5).asInstanceOf[T6], 141 | args.productElement(6).asInstanceOf[T7], 142 | args.productElement(7).asInstanceOf[T8], 143 | args.productElement(8).asInstanceOf[T9], 144 | args.productElement(9).asInstanceOf[T10] 145 | ) 146 | 147 | class FunctionAdapter11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R]( 148 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) => R 149 | ) extends Function1[Product, R]: 150 | 151 | def apply(args: Product) = 152 | assert(args.productArity == 11) 153 | f( 154 | args.productElement(0).asInstanceOf[T1], 155 | args.productElement(1).asInstanceOf[T2], 156 | args.productElement(2).asInstanceOf[T3], 157 | args.productElement(3).asInstanceOf[T4], 158 | args.productElement(4).asInstanceOf[T5], 159 | args.productElement(5).asInstanceOf[T6], 160 | args.productElement(6).asInstanceOf[T7], 161 | args.productElement(7).asInstanceOf[T8], 162 | args.productElement(8).asInstanceOf[T9], 163 | args.productElement(9).asInstanceOf[T10], 164 | args.productElement(10).asInstanceOf[T11] 165 | ) 166 | 167 | class FunctionAdapter12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R]( 168 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) => R 169 | ) extends Function1[Product, R]: 170 | 171 | def apply(args: Product) = 172 | assert(args.productArity == 12) 173 | f( 174 | args.productElement(0).asInstanceOf[T1], 175 | args.productElement(1).asInstanceOf[T2], 176 | args.productElement(2).asInstanceOf[T3], 177 | args.productElement(3).asInstanceOf[T4], 178 | args.productElement(4).asInstanceOf[T5], 179 | args.productElement(5).asInstanceOf[T6], 180 | args.productElement(6).asInstanceOf[T7], 181 | args.productElement(7).asInstanceOf[T8], 182 | args.productElement(8).asInstanceOf[T9], 183 | args.productElement(9).asInstanceOf[T10], 184 | args.productElement(10).asInstanceOf[T11], 185 | args.productElement(11).asInstanceOf[T12] 186 | ) 187 | 188 | class FunctionAdapter13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R]( 189 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) => R 190 | ) extends Function1[Product, R]: 191 | 192 | def apply(args: Product) = 193 | assert(args.productArity == 13) 194 | f( 195 | args.productElement(0).asInstanceOf[T1], 196 | args.productElement(1).asInstanceOf[T2], 197 | args.productElement(2).asInstanceOf[T3], 198 | args.productElement(3).asInstanceOf[T4], 199 | args.productElement(4).asInstanceOf[T5], 200 | args.productElement(5).asInstanceOf[T6], 201 | args.productElement(6).asInstanceOf[T7], 202 | args.productElement(7).asInstanceOf[T8], 203 | args.productElement(8).asInstanceOf[T9], 204 | args.productElement(9).asInstanceOf[T10], 205 | args.productElement(10).asInstanceOf[T11], 206 | args.productElement(11).asInstanceOf[T12], 207 | args.productElement(12).asInstanceOf[T13] 208 | ) 209 | 210 | class FunctionAdapter14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R]( 211 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) => R 212 | ) extends Function1[Product, R]: 213 | 214 | def apply(args: Product) = 215 | assert(args.productArity == 14) 216 | f( 217 | args.productElement(0).asInstanceOf[T1], 218 | args.productElement(1).asInstanceOf[T2], 219 | args.productElement(2).asInstanceOf[T3], 220 | args.productElement(3).asInstanceOf[T4], 221 | args.productElement(4).asInstanceOf[T5], 222 | args.productElement(5).asInstanceOf[T6], 223 | args.productElement(6).asInstanceOf[T7], 224 | args.productElement(7).asInstanceOf[T8], 225 | args.productElement(8).asInstanceOf[T9], 226 | args.productElement(9).asInstanceOf[T10], 227 | args.productElement(10).asInstanceOf[T11], 228 | args.productElement(11).asInstanceOf[T12], 229 | args.productElement(12).asInstanceOf[T13], 230 | args.productElement(13).asInstanceOf[T14] 231 | ) 232 | 233 | class FunctionAdapter15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R]( 234 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) => R 235 | ) extends Function1[Product, R]: 236 | 237 | def apply(args: Product) = 238 | assert(args.productArity == 15) 239 | f( 240 | args.productElement(0).asInstanceOf[T1], 241 | args.productElement(1).asInstanceOf[T2], 242 | args.productElement(2).asInstanceOf[T3], 243 | args.productElement(3).asInstanceOf[T4], 244 | args.productElement(4).asInstanceOf[T5], 245 | args.productElement(5).asInstanceOf[T6], 246 | args.productElement(6).asInstanceOf[T7], 247 | args.productElement(7).asInstanceOf[T8], 248 | args.productElement(8).asInstanceOf[T9], 249 | args.productElement(9).asInstanceOf[T10], 250 | args.productElement(10).asInstanceOf[T11], 251 | args.productElement(11).asInstanceOf[T12], 252 | args.productElement(12).asInstanceOf[T13], 253 | args.productElement(13).asInstanceOf[T14], 254 | args.productElement(14).asInstanceOf[T15] 255 | ) 256 | 257 | class FunctionAdapter16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R]( 258 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) => R 259 | ) extends Function1[Product, R]: 260 | 261 | def apply(args: Product) = 262 | assert(args.productArity == 16) 263 | f( 264 | args.productElement(0).asInstanceOf[T1], 265 | args.productElement(1).asInstanceOf[T2], 266 | args.productElement(2).asInstanceOf[T3], 267 | args.productElement(3).asInstanceOf[T4], 268 | args.productElement(4).asInstanceOf[T5], 269 | args.productElement(5).asInstanceOf[T6], 270 | args.productElement(6).asInstanceOf[T7], 271 | args.productElement(7).asInstanceOf[T8], 272 | args.productElement(8).asInstanceOf[T9], 273 | args.productElement(9).asInstanceOf[T10], 274 | args.productElement(10).asInstanceOf[T11], 275 | args.productElement(11).asInstanceOf[T12], 276 | args.productElement(12).asInstanceOf[T13], 277 | args.productElement(13).asInstanceOf[T14], 278 | args.productElement(14).asInstanceOf[T15], 279 | args.productElement(15).asInstanceOf[T16], 280 | ) 281 | 282 | class FunctionAdapter17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R]( 283 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) => R 284 | ) extends Function1[Product, R]: 285 | 286 | def apply(args: Product) = 287 | assert(args.productArity == 17) 288 | f( 289 | args.productElement(0).asInstanceOf[T1], 290 | args.productElement(1).asInstanceOf[T2], 291 | args.productElement(2).asInstanceOf[T3], 292 | args.productElement(3).asInstanceOf[T4], 293 | args.productElement(4).asInstanceOf[T5], 294 | args.productElement(5).asInstanceOf[T6], 295 | args.productElement(6).asInstanceOf[T7], 296 | args.productElement(7).asInstanceOf[T8], 297 | args.productElement(8).asInstanceOf[T9], 298 | args.productElement(9).asInstanceOf[T10], 299 | args.productElement(10).asInstanceOf[T11], 300 | args.productElement(11).asInstanceOf[T12], 301 | args.productElement(12).asInstanceOf[T13], 302 | args.productElement(13).asInstanceOf[T14], 303 | args.productElement(14).asInstanceOf[T15], 304 | args.productElement(15).asInstanceOf[T16], 305 | args.productElement(16).asInstanceOf[T17], 306 | ) 307 | 308 | class FunctionAdapter18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R]( 309 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) => R 310 | ) extends Function1[Product, R]: 311 | 312 | def apply(args: Product) = 313 | assert(args.productArity == 18) 314 | f( 315 | args.productElement(0).asInstanceOf[T1], 316 | args.productElement(1).asInstanceOf[T2], 317 | args.productElement(2).asInstanceOf[T3], 318 | args.productElement(3).asInstanceOf[T4], 319 | args.productElement(4).asInstanceOf[T5], 320 | args.productElement(5).asInstanceOf[T6], 321 | args.productElement(6).asInstanceOf[T7], 322 | args.productElement(7).asInstanceOf[T8], 323 | args.productElement(8).asInstanceOf[T9], 324 | args.productElement(9).asInstanceOf[T10], 325 | args.productElement(10).asInstanceOf[T11], 326 | args.productElement(11).asInstanceOf[T12], 327 | args.productElement(12).asInstanceOf[T13], 328 | args.productElement(13).asInstanceOf[T14], 329 | args.productElement(14).asInstanceOf[T15], 330 | args.productElement(15).asInstanceOf[T16], 331 | args.productElement(16).asInstanceOf[T17], 332 | args.productElement(17).asInstanceOf[T18], 333 | ) 334 | 335 | class FunctionAdapter19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R]( 336 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) => R 337 | ) extends Function1[Product, R]: 338 | 339 | def apply(args: Product) = 340 | assert(args.productArity == 19) 341 | f( 342 | args.productElement(0).asInstanceOf[T1], 343 | args.productElement(1).asInstanceOf[T2], 344 | args.productElement(2).asInstanceOf[T3], 345 | args.productElement(3).asInstanceOf[T4], 346 | args.productElement(4).asInstanceOf[T5], 347 | args.productElement(5).asInstanceOf[T6], 348 | args.productElement(6).asInstanceOf[T7], 349 | args.productElement(7).asInstanceOf[T8], 350 | args.productElement(8).asInstanceOf[T9], 351 | args.productElement(9).asInstanceOf[T10], 352 | args.productElement(10).asInstanceOf[T11], 353 | args.productElement(11).asInstanceOf[T12], 354 | args.productElement(12).asInstanceOf[T13], 355 | args.productElement(13).asInstanceOf[T14], 356 | args.productElement(14).asInstanceOf[T15], 357 | args.productElement(15).asInstanceOf[T16], 358 | args.productElement(16).asInstanceOf[T17], 359 | args.productElement(17).asInstanceOf[T18], 360 | args.productElement(18).asInstanceOf[T19], 361 | ) 362 | 363 | class FunctionAdapter20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R]( 364 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20) => R 365 | ) extends Function1[Product, R]: 366 | 367 | def apply(args: Product) = 368 | assert(args.productArity == 20) 369 | f( 370 | args.productElement(0).asInstanceOf[T1], 371 | args.productElement(1).asInstanceOf[T2], 372 | args.productElement(2).asInstanceOf[T3], 373 | args.productElement(3).asInstanceOf[T4], 374 | args.productElement(4).asInstanceOf[T5], 375 | args.productElement(5).asInstanceOf[T6], 376 | args.productElement(6).asInstanceOf[T7], 377 | args.productElement(7).asInstanceOf[T8], 378 | args.productElement(8).asInstanceOf[T9], 379 | args.productElement(9).asInstanceOf[T10], 380 | args.productElement(10).asInstanceOf[T11], 381 | args.productElement(11).asInstanceOf[T12], 382 | args.productElement(12).asInstanceOf[T13], 383 | args.productElement(13).asInstanceOf[T14], 384 | args.productElement(14).asInstanceOf[T15], 385 | args.productElement(15).asInstanceOf[T16], 386 | args.productElement(16).asInstanceOf[T17], 387 | args.productElement(17).asInstanceOf[T18], 388 | args.productElement(18).asInstanceOf[T19], 389 | args.productElement(19).asInstanceOf[T20], 390 | ) 391 | 392 | class FunctionAdapter21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R]( 393 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21) => R 394 | ) extends Function1[Product, R]: 395 | 396 | def apply(args: Product) = 397 | assert(args.productArity == 21) 398 | f( 399 | args.productElement(0).asInstanceOf[T1], 400 | args.productElement(1).asInstanceOf[T2], 401 | args.productElement(2).asInstanceOf[T3], 402 | args.productElement(3).asInstanceOf[T4], 403 | args.productElement(4).asInstanceOf[T5], 404 | args.productElement(5).asInstanceOf[T6], 405 | args.productElement(6).asInstanceOf[T7], 406 | args.productElement(7).asInstanceOf[T8], 407 | args.productElement(8).asInstanceOf[T9], 408 | args.productElement(9).asInstanceOf[T10], 409 | args.productElement(10).asInstanceOf[T11], 410 | args.productElement(11).asInstanceOf[T12], 411 | args.productElement(12).asInstanceOf[T13], 412 | args.productElement(13).asInstanceOf[T14], 413 | args.productElement(14).asInstanceOf[T15], 414 | args.productElement(15).asInstanceOf[T16], 415 | args.productElement(16).asInstanceOf[T17], 416 | args.productElement(17).asInstanceOf[T18], 417 | args.productElement(18).asInstanceOf[T19], 418 | args.productElement(19).asInstanceOf[T20], 419 | args.productElement(20).asInstanceOf[T21], 420 | ) 421 | 422 | class FunctionAdapter22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R]( 423 | f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22) => R 424 | ) extends Function1[Product, R]: 425 | 426 | def apply(args: Product) = 427 | assert(args.productArity == 22) 428 | f( 429 | args.productElement(0).asInstanceOf[T1], 430 | args.productElement(1).asInstanceOf[T2], 431 | args.productElement(2).asInstanceOf[T3], 432 | args.productElement(3).asInstanceOf[T4], 433 | args.productElement(4).asInstanceOf[T5], 434 | args.productElement(5).asInstanceOf[T6], 435 | args.productElement(6).asInstanceOf[T7], 436 | args.productElement(7).asInstanceOf[T8], 437 | args.productElement(8).asInstanceOf[T9], 438 | args.productElement(9).asInstanceOf[T10], 439 | args.productElement(10).asInstanceOf[T11], 440 | args.productElement(11).asInstanceOf[T12], 441 | args.productElement(12).asInstanceOf[T13], 442 | args.productElement(13).asInstanceOf[T14], 443 | args.productElement(14).asInstanceOf[T15], 444 | args.productElement(15).asInstanceOf[T16], 445 | args.productElement(16).asInstanceOf[T17], 446 | args.productElement(17).asInstanceOf[T18], 447 | args.productElement(18).asInstanceOf[T19], 448 | args.productElement(19).asInstanceOf[T20], 449 | args.productElement(20).asInstanceOf[T21], 450 | args.productElement(21).asInstanceOf[T22], 451 | ) 452 | 453 | // format: on 454 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/functions/MockFunctions.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.functions 2 | 3 | import eu.monniot.scala3mock.context.MockContext 4 | import eu.monniot.scala3mock.Default 5 | 6 | object MockFunctions extends MockFunctions 7 | trait MockFunctions { 8 | // format: off 9 | def mockFunction[R: Default](using ctx: MockContext) = 10 | new MockFunction0[R](ctx, "MockFunction0") 11 | def mockFunction[T1, R: Default](using ctx: MockContext) = 12 | new MockFunction1[T1, R](ctx, "MockFunction1") 13 | def mockFunction[T1, T2, R: Default](using ctx: MockContext) = 14 | new MockFunction2[T1, T2, R](ctx, "MockFunction2") 15 | def mockFunction[T1, T2, T3, R: Default](using ctx: MockContext) = 16 | new MockFunction3[T1, T2, T3, R](ctx, "MockFunction3") 17 | def mockFunction[T1, T2, T3, T4, R: Default](using ctx: MockContext) = 18 | new MockFunction4[T1, T2, T3, T4, R](ctx, "MockFunction4") 19 | def mockFunction[T1, T2, T3, T4, T5, R: Default](using ctx: MockContext) = 20 | new MockFunction5[T1, T2, T3, T4, T5, R](ctx, "MockFunction5") 21 | def mockFunction[T1, T2, T3, T4, T5, T6, R: Default](using ctx: MockContext) = 22 | new MockFunction6[T1, T2, T3, T4, T5, T6, R](ctx, "MockFunction6") 23 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, R: Default](using 24 | ctx: MockContext 25 | ) = 26 | new MockFunction7[T1, T2, T3, T4, T5, T6, T7, R](ctx, "MockFunction7") 27 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, R: Default](using 28 | ctx: MockContext 29 | ) = 30 | new MockFunction8[T1, T2, T3, T4, T5, T6, T7, T8, R](ctx, "MockFunction8") 31 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, R: Default](using 32 | ctx: MockContext 33 | ) = 34 | new MockFunction9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R]( 35 | ctx, 36 | "MockFunction9" 37 | ) 38 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R: Default](using 39 | ctx: MockContext 40 | ) = 41 | new MockFunction10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R]( 42 | ctx, 43 | "MockFunction10" 44 | ) 45 | 46 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R: Default](using 47 | ctx: MockContext 48 | ) = 49 | new MockFunction11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R]( 50 | ctx, 51 | "MockFunction11" 52 | ) 53 | 54 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R: Default](using 55 | ctx: MockContext 56 | ) = 57 | new MockFunction12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R]( 58 | ctx, 59 | "MockFunction12" 60 | ) 61 | 62 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R: Default](using 63 | ctx: MockContext 64 | ) = 65 | new MockFunction13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R]( 66 | ctx, 67 | "MockFunction13" 68 | ) 69 | 70 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R: Default](using 71 | ctx: MockContext 72 | ) = 73 | new MockFunction14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R]( 74 | ctx, 75 | "MockFunction14" 76 | ) 77 | 78 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R: Default](using 79 | ctx: MockContext 80 | ) = 81 | new MockFunction15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R]( 82 | ctx, 83 | "MockFunction15" 84 | ) 85 | 86 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R: Default](using 87 | ctx: MockContext 88 | ) = 89 | new MockFunction16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R]( 90 | ctx, 91 | "MockFunction16" 92 | ) 93 | 94 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R: Default](using 95 | ctx: MockContext 96 | ) = 97 | new MockFunction17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R]( 98 | ctx, 99 | "MockFunction17" 100 | ) 101 | 102 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R: Default](using 103 | ctx: MockContext 104 | ) = 105 | new MockFunction18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R]( 106 | ctx, 107 | "MockFunction18" 108 | ) 109 | 110 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R: Default](using 111 | ctx: MockContext 112 | ) = 113 | new MockFunction19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R]( 114 | ctx, 115 | "MockFunction19" 116 | ) 117 | 118 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R: Default](using 119 | ctx: MockContext 120 | ) = 121 | new MockFunction20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R]( 122 | ctx, 123 | "MockFunction20" 124 | ) 125 | 126 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R: Default](using 127 | ctx: MockContext 128 | ) = 129 | new MockFunction21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R]( 130 | ctx, 131 | "MockFunction21" 132 | ) 133 | 134 | def mockFunction[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R: Default](using 135 | ctx: MockContext 136 | ) = 137 | new MockFunction22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R]( 138 | ctx, 139 | "MockFunction22" 140 | ) 141 | 142 | // format: on 143 | } 144 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/handlers/CallHandler.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.handlers 2 | 3 | import eu.monniot.scala3mock.functions.{ 4 | FakeFunction, 5 | FunctionAdapter0, 6 | FunctionAdapter1, 7 | FunctionAdapter3 8 | } 9 | import eu.monniot.scala3mock.context.Call 10 | import eu.monniot.scala3mock.matchers.ArgumentMatcher 11 | import eu.monniot.scala3mock.Default 12 | 13 | class CallHandler[R: Default]( 14 | val target: FakeFunction, 15 | val argumentMatcher: Product => Boolean 16 | ) extends Handler: 17 | import eu.monniot.scala3mock.handlers.CallHandler._ 18 | 19 | type Derived <: CallHandler[R] 20 | 21 | def repeated(range: Range): Derived = 22 | expectedCalls = range 23 | this.asInstanceOf[Derived] 24 | 25 | def repeated(atLeast: Int = 1, atMost: Int = Int.MaxValue - 1): Derived = 26 | repeated(atLeast to atMost) 27 | 28 | def exactly(count: Int): Derived = repeated(count to count) 29 | 30 | def never = repeated(NEVER) 31 | def once = repeated(ONCE) 32 | def twice = repeated(TWICE) 33 | 34 | def anyNumberOfTimes = repeated(ANY_NUMBER_OF_TIMES) 35 | def atLeastOnce = repeated(AT_LEAST_ONCE) 36 | def atLeastTwice = repeated(AT_LEAST_TWICE) 37 | 38 | def noMoreThanOnce = repeated(NO_MORE_THAN_ONCE) 39 | def noMoreThanTwice = repeated(NO_MORE_THAN_TWICE) 40 | 41 | def returns(value: R) = onCall({ _ => value }) 42 | def returning(value: R) = returns(value) 43 | 44 | def throws(e: Throwable) = onCall({ _ => throw e }) 45 | def throwing(e: Throwable) = throws(e) 46 | 47 | def onCall(handler: Product => R) = 48 | onCallHandler = handler 49 | this.asInstanceOf[Derived] 50 | 51 | override def toString = 52 | val expected = expectedCalls match 53 | case NEVER => "never" 54 | case ONCE => "once" 55 | case TWICE => "twice" 56 | case ANY_NUMBER_OF_TIMES => "any number of times" 57 | case AT_LEAST_ONCE => "at least once" 58 | case AT_LEAST_TWICE => "at least twice" 59 | case NO_MORE_THAN_ONCE => "no more than once" 60 | case NO_MORE_THAN_TWICE => "no more than twice" 61 | case r if r.size == 1 => s"${r.start} times" 62 | case r => s"between ${r.start} and ${r.end} times" 63 | val actual = actualCalls match 64 | case 0 => "never called" 65 | case 1 => "called once" 66 | case 2 => "called twice" 67 | case n => s"called $n times" 68 | val satisfied = if isSatisfied then "" else " - UNSATISFIED" 69 | s"$target$argumentMatcher $expected ($actual$satisfied)" 70 | 71 | def handle(call: Call) = 72 | if target == call.target && !isExhausted && argumentMatcher(call.arguments) 73 | then 74 | actualCalls += 1 75 | Some(onCallHandler(call.arguments)) 76 | else None 77 | 78 | def verify(call: Call) = false 79 | 80 | def isSatisfied = expectedCalls contains actualCalls 81 | 82 | def isExhausted = expectedCalls.last <= actualCalls 83 | 84 | def reset(): Unit = actualCalls = 0 85 | 86 | var expectedCalls: Range = 1 to 1 87 | var actualCalls: Int = 0 88 | var onCallHandler: Product => R = { _ => Default[R].default } 89 | 90 | object CallHandler: 91 | 92 | val NEVER = 0 to 0 93 | val ONCE = 1 to 1 94 | val TWICE = 2 to 2 95 | 96 | val ANY_NUMBER_OF_TIMES = 0 to scala.Int.MaxValue - 1 97 | val AT_LEAST_ONCE = 1 to scala.Int.MaxValue - 1 98 | val AT_LEAST_TWICE = 2 to scala.Int.MaxValue - 1 99 | 100 | val NO_MORE_THAN_ONCE = 0 to 1 101 | val NO_MORE_THAN_TWICE = 0 to 2 102 | 103 | trait Verify { self: CallHandler[_] => 104 | 105 | override def handle(call: Call) = sys.error( 106 | "verify should appear after all code under test has been exercised" 107 | ) 108 | 109 | override def verify(call: Call) = 110 | if self.target == call.target && argumentMatcher(call.arguments) then 111 | actualCalls += 1 112 | true 113 | else false 114 | } 115 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/handlers/Handler.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.handlers 2 | 3 | import eu.monniot.scala3mock.context.Call 4 | 5 | trait Handler: 6 | 7 | def handle(call: Call): Option[Any] 8 | 9 | def verify(call: Call): Boolean 10 | 11 | def isSatisfied: Boolean 12 | 13 | def reset(): Unit 14 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/handlers/Handlers.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.handlers 2 | 3 | import scala.collection.mutable.ListBuffer 4 | 5 | abstract class Handlers extends Handler: 6 | 7 | def add(handler: Handler): Unit = handlers += handler 8 | def list: Iterable[Handler] = handlers 9 | 10 | def isSatisfied: Boolean = handlers forall (_.isSatisfied) 11 | 12 | override def toString: String = handlers 13 | .flatMap { h => 14 | scala.Predef.augmentString(h.toString).linesIterator.toArray.map { l => 15 | " " + l 16 | } 17 | } 18 | .mkString(s"$prefix {\n", "\n", "\n}") 19 | 20 | protected val handlers = new ListBuffer[Handler] 21 | 22 | protected val prefix: String 23 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/handlers/UnorderedHandlers.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.handlers 2 | 3 | import eu.monniot.scala3mock.context.Call 4 | import scala.util.control.NonLocalReturns.* 5 | 6 | class UnorderedHandlers(logging: Boolean = false) extends Handlers: 7 | 8 | def handle(call: Call): Option[Any] = this.synchronized { 9 | if logging then println(s"handling unordered call $call") 10 | 11 | findFirstOpt(handlers, _.handle(call)) 12 | } 13 | 14 | def verify(call: Call): Boolean = this.synchronized { 15 | if logging then println(s"verifying unordered call $call") 16 | 17 | handlers.find(_.verify(call)).isDefined 18 | } 19 | 20 | def reset(): Unit = handlers.foreach(_.reset()) 21 | 22 | protected val prefix = "inAnyOrder" 23 | 24 | /** Finds the first element of `iter` satisfying a predicate, if any. 25 | * 26 | * @param iter 27 | * the collection of elements 28 | * @param p 29 | * the predicate used to test elements. 30 | * @return 31 | * an option value containing the first element in the $coll that satisfies 32 | * `p.isDefined`, or `None` if none exists. 33 | */ 34 | private def findFirstOpt[A, B]( 35 | iter: IterableOnce[A], 36 | p: A => Option[B] 37 | ): Option[B] = { 38 | val it = iter.iterator 39 | while (it.hasNext) { 40 | val a = it.next() 41 | val mayB = p(a) 42 | if (mayB.isDefined) return mayB 43 | } 44 | None 45 | } 46 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/macros/Mocks.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.macros 2 | 3 | import eu.monniot.scala3mock.context.{Mock, MockContext} 4 | import eu.monniot.scala3mock.functions._ 5 | import eu.monniot.scala3mock.Default 6 | 7 | trait Mocks: 8 | inline def mock[T](using MockContext): T = MockImpl[T] 9 | 10 | /** Like mock but enable internal debug log for our macros. Use when you have 11 | * found an issue and want to report it to the maintainers. 12 | */ 13 | inline def mockWithDebuggingOutput[T](using MockContext): T = 14 | MockImpl.debug[T] 15 | 16 | import scala.quoted.* 17 | 18 | // The Default constraint on the return type somehow help the compiler 19 | // not infer a type that's not the function type. For example without it 20 | // a mocked `Int => List[Int]` could be set up as `when(f).returns(List("a"))` 21 | // and the compiler would happily infer `MockFunction1[Int, Thing[? >: Int & String <: Int | String]]` 22 | // For some reasons with the type class constraint that is not the case anymore. 23 | // format: off 24 | 25 | inline def when[R: Default](inline f: () => R): MockFunction0[R] = 26 | (${ WhenImpl('f) }) 27 | inline def when[T1, R: Default](inline f: T1 => R): MockFunction1[T1, R] = 28 | (${ WhenImpl('f) }) 29 | inline def when[T1, T2, R: Default]( 30 | inline f: (T1, T2) => R 31 | ): MockFunction2[T1, T2, R] = (${ WhenImpl('f) }) 32 | inline def when[T1, T2, T3, R: Default]( 33 | inline f: (T1, T2, T3) => R 34 | ): MockFunction3[T1, T2, T3, R] = (${ WhenImpl('f) }) 35 | inline def when[T1, T2, T3, T4, R: Default]( 36 | inline f: (T1, T2, T3, T4) => R 37 | ): MockFunction4[T1, T2, T3, T4, R] = (${ WhenImpl('f) }) 38 | inline def when[T1, T2, T3, T4, T5, R: Default]( 39 | inline f: (T1, T2, T3, T4, T5) => R 40 | ): MockFunction5[T1, T2, T3, T4, T5, R] = (${ WhenImpl('f) }) 41 | inline def when[T1, T2, T3, T4, T5, T6, R: Default]( 42 | inline f: (T1, T2, T3, T4, T5, T6) => R 43 | ): MockFunction6[T1, T2, T3, T4, T5, T6, R] = (${ WhenImpl('f) }) 44 | inline def when[T1, T2, T3, T4, T5, T6, T7, R: Default]( 45 | inline f: (T1, T2, T3, T4, T5, T6, T7) => R 46 | ): MockFunction7[T1, T2, T3, T4, T5, T6, T7, R] = (${ WhenImpl('f) }) 47 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, R: Default]( 48 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8) => R 49 | ): MockFunction8[T1, T2, T3, T4, T5, T6, T7, T8, R] = (${ WhenImpl('f) }) 50 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, R: Default]( 51 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9) => R 52 | ): MockFunction9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R] = (${ WhenImpl('f) }) 53 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R: Default]( 54 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) => R 55 | ): MockFunction10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R] = 56 | (${ WhenImpl('f) }) 57 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R: Default]( 58 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) => R 59 | ): MockFunction11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R] = 60 | (${ WhenImpl('f) }) 61 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R: Default]( 62 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) => R 63 | ): MockFunction12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R] = 64 | (${ WhenImpl('f) }) 65 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R: Default]( 66 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) => R 67 | ): MockFunction13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R] = 68 | (${ WhenImpl('f) }) 69 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R: Default]( 70 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) => R 71 | ): MockFunction14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R] = 72 | (${ WhenImpl('f) }) 73 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R: Default]( 74 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) => R 75 | ): MockFunction15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R] = 76 | (${ WhenImpl('f) }) 77 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R: Default]( 78 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) => R 79 | ): MockFunction16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R] = 80 | (${ WhenImpl('f) }) 81 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R: Default]( 82 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) => R 83 | ): MockFunction17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R] = 84 | (${ WhenImpl('f) }) 85 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R: Default]( 86 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) => R 87 | ): MockFunction18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R] = 88 | (${ WhenImpl('f) }) 89 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R: Default]( 90 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) => R 91 | ): MockFunction19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R] = 92 | (${ WhenImpl('f) }) 93 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R: Default]( 94 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20) => R 95 | ): MockFunction20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R] = 96 | (${ WhenImpl('f) }) 97 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R: Default]( 98 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21) => R 99 | ): MockFunction21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R] = 100 | (${ WhenImpl('f) }) 101 | inline def when[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R: Default]( 102 | inline f: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22) => R 103 | ): MockFunction22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R] = 104 | (${ WhenImpl('f) }) 105 | 106 | // format: on 107 | 108 | object Mocks extends Mocks 109 | 110 | export Mocks.* 111 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/macros/PrintAst.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.macros 2 | 3 | object PrintAst: 4 | 5 | import scala.quoted.* 6 | 7 | inline def apply[T](inline stuff: T) = 8 | ${ inspectExpr('stuff) } 9 | 10 | inline def apply[T] = 11 | ${ inspectType[T] } 12 | 13 | private def inspectExpr[T](x: Expr[T])(using Quotes, Type[T]): Expr[Any] = 14 | import quotes.reflect.* 15 | 16 | val term = x.asTerm 17 | println(s"===========Expression of type ${Type.show[T]}=========:") 18 | println() 19 | println(term.show(using Printer.TreeAnsiCode)) 20 | println() 21 | println(term.show(using Printer.TreeStructure)) 22 | println() 23 | println("===========================") 24 | 25 | x 26 | 27 | private def inspectType[T](using Type[T], Quotes): Expr[Any] = 28 | import quotes.reflect.* 29 | 30 | val typeRepr: TypeRepr = TypeRepr.of[T].dealias 31 | println(s"===========Type ${Type.show[T]}=========:") 32 | println() 33 | println(s"typeSymbol=${typeRepr.typeSymbol}; ") 34 | println() 35 | println(typeRepr.typeSymbol.tree.show(using Printer.TreeAnsiCode)) 36 | println() 37 | println(typeRepr.typeSymbol.tree.show(using Printer.TreeStructure)) 38 | println() 39 | println("===========================") 40 | 41 | '{ () } 42 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/macros/WhenImpl.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.macros 2 | 3 | import eu.monniot.scala3mock.functions.* 4 | import eu.monniot.scala3mock.context.Mock 5 | import eu.monniot.scala3mock.Default 6 | 7 | private[scala3mock] object WhenImpl: 8 | 9 | import scala.quoted.* 10 | 11 | // We disable formatting for the apply & impl functions so that we can save line height. 12 | // Without disable, appy12 would take around 36 lines vs the current one. impl is even worse. 13 | // format: off 14 | 15 | def apply[R](f: Expr[() => R])(using Type[R], Quotes): Expr[MockFunction0[R]] = 16 | '{${createMockFunction(f)}.asInstanceOf[MockFunction0[R]]} 17 | 18 | def apply[T1, R](f: Expr[T1 => R])(using Type[T1], Type[R], Quotes): Expr[MockFunction1[T1, R]] = 19 | '{ 20 | val mf = ${createMockFunction(f)}.asInstanceOf[MockFunction1[T1, R]] 21 | 22 | // used to debug what it is being infered 23 | /* 24 | println(s"""|selected MockFunction1 = $mf 25 | |T1 = ${${Expr(Type.show[T1])}} 26 | |T1.repr = ${${Expr(TypeRepr.of[T1].toString())}} 27 | |R = ${${Expr(Type.show[R])}} 28 | |""".stripMargin) 29 | */ 30 | mf 31 | } 32 | 33 | def apply[T1, T2, R](f: Expr[(T1, T2) => R])(using Type[T1], Type[T2], Type[R], Quotes): Expr[MockFunction2[T1, T2, R]] = 34 | '{${createMockFunction(f)}.asInstanceOf[MockFunction2[T1, T2, R]]} 35 | 36 | def apply[T1, T2, T3, R](f: Expr[(T1, T2, T3) => R])(using Type[T1], Type[T2], Type[T3], Type[R], Quotes): Expr[MockFunction3[T1, T2, T3, R]] = 37 | '{${createMockFunction(f)}.asInstanceOf[MockFunction3[T1, T2, T3, R]]} 38 | 39 | def apply[T1, T2, T3, T4, R](f: Expr[(T1, T2, T3, T4) => R])(using Type[T1], Type[T2], Type[T3], Type[T4], Type[R], Quotes): Expr[MockFunction4[T1, T2, T3, T4, R]] = 40 | '{${createMockFunction(f)}.asInstanceOf[MockFunction4[T1, T2, T3, T4, R]]} 41 | 42 | def apply[T1, T2, T3, T4, T5, R](f: Expr[(T1, T2, T3, T4, T5) => R])(using 43 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], 44 | Type[R], Quotes): Expr[MockFunction5[T1, T2, T3, T4, T5, R]] = 45 | '{${createMockFunction(f)}.asInstanceOf[MockFunction5[T1, T2, T3, T4, T5, R]]} 46 | 47 | def apply[T1, T2, T3, T4, T5, T6, R](f: Expr[(T1, T2, T3, T4, T5, T6) => R])(using 48 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], 49 | Type[R], Quotes): Expr[MockFunction6[T1, T2, T3, T4, T5, T6, R]] = 50 | '{${createMockFunction(f)}.asInstanceOf[MockFunction6[T1, T2, T3, T4, T5, T6, R]]} 51 | 52 | def apply[T1, T2, T3, T4, T5, T6, T7, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7) => R])(using 53 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], 54 | Type[R], Quotes): Expr[MockFunction7[T1, T2, T3, T4, T5, T6, T7, R]] = 55 | '{${createMockFunction(f)}.asInstanceOf[MockFunction7[T1, T2, T3, T4, T5, T6, T7, R]]} 56 | 57 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8) => R])(using 58 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8], 59 | Type[R], Quotes): Expr[MockFunction8[T1, T2, T3, T4, T5, T6, T7, T8, R]] = 60 | '{${createMockFunction(f)}.asInstanceOf[MockFunction8[T1, T2, T3, T4, T5, T6, T7, T8, R]]} 61 | 62 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9) => R])(using 63 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9], 64 | Type[R], Quotes): Expr[MockFunction9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R]] = 65 | '{${createMockFunction(f)}.asInstanceOf[MockFunction9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R]]} 66 | 67 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) => R])(using 68 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10], 69 | Type[R], Quotes): Expr[MockFunction10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R]] = 70 | '{${createMockFunction(f)}.asInstanceOf[MockFunction10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R]]} 71 | 72 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) => R])(using 73 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10],Type[T11], 74 | Type[R], Quotes): Expr[MockFunction11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R]] = 75 | '{${createMockFunction(f)}.asInstanceOf[MockFunction11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R]]} 76 | 77 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) => R])(using 78 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10],Type[T11],Type[T12], 79 | Type[R], Quotes): Expr[MockFunction12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R]] = 80 | '{${createMockFunction(f)}.asInstanceOf[MockFunction12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R]]} 81 | 82 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) => R])(using 83 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10],Type[T11],Type[T12],Type[T13], 84 | Type[R], Quotes): Expr[MockFunction13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R]] = 85 | '{${createMockFunction(f)}.asInstanceOf[MockFunction13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R]]} 86 | 87 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) => R])(using 88 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10],Type[T11],Type[T12],Type[T13],Type[T14], 89 | Type[R], Quotes): Expr[MockFunction14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R]] = 90 | '{${createMockFunction(f)}.asInstanceOf[MockFunction14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R]]} 91 | 92 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) => R])(using 93 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10],Type[T11],Type[T12],Type[T13],Type[T14],Type[T15], 94 | Type[R], Quotes): Expr[MockFunction15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R]] = 95 | '{${createMockFunction(f)}.asInstanceOf[MockFunction15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R]]} 96 | 97 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) => R])(using 98 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10],Type[T11],Type[T12],Type[T13],Type[T14],Type[T15],Type[T16], 99 | Type[R], Quotes): Expr[MockFunction16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R]] = 100 | '{${createMockFunction(f)}.asInstanceOf[MockFunction16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R]]} 101 | 102 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) => R])(using 103 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10],Type[T11],Type[T12],Type[T13],Type[T14],Type[T15],Type[T16],Type[T17], 104 | Type[R], Quotes): Expr[MockFunction17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R]] = 105 | '{${createMockFunction(f)}.asInstanceOf[MockFunction17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, R]]} 106 | 107 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) => R])(using 108 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10],Type[T11],Type[T12],Type[T13],Type[T14],Type[T15],Type[T16],Type[T17],Type[T18], 109 | Type[R], Quotes): Expr[MockFunction18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R]] = 110 | '{${createMockFunction(f)}.asInstanceOf[MockFunction18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, R]]} 111 | 112 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) => R])(using 113 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10],Type[T11],Type[T12],Type[T13],Type[T14],Type[T15],Type[T16],Type[T17],Type[T18],Type[T19], 114 | Type[R], Quotes): Expr[MockFunction19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R]] = 115 | '{${createMockFunction(f)}.asInstanceOf[MockFunction19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, R]]} 116 | 117 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20) => R])(using 118 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10],Type[T11],Type[T12],Type[T13],Type[T14],Type[T15],Type[T16],Type[T17],Type[T18],Type[T19],Type[T20], 119 | Type[R], Quotes): Expr[MockFunction20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R]] = 120 | '{${createMockFunction(f)}.asInstanceOf[MockFunction20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, R]]} 121 | 122 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21) => R])(using 123 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10],Type[T11],Type[T12],Type[T13],Type[T14],Type[T15],Type[T16],Type[T17],Type[T18],Type[T19],Type[T20],Type[T21], 124 | Type[R], Quotes): Expr[MockFunction21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R]] = 125 | '{${createMockFunction(f)}.asInstanceOf[MockFunction21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, R]]} 126 | 127 | def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R](f: Expr[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22) => R])(using 128 | Type[T1], Type[T2], Type[T3], Type[T4], Type[T5], Type[T6], Type[T7], Type[T8],Type[T9],Type[T10],Type[T11],Type[T12],Type[T13],Type[T14],Type[T15],Type[T16],Type[T17],Type[T18],Type[T19],Type[T20],Type[T21],Type[T22], 129 | Type[R], Quotes): Expr[MockFunction22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R]] = 130 | '{${createMockFunction(f)}.asInstanceOf[MockFunction22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, R]]} 131 | 132 | // format: on 133 | 134 | def createMockFunction(expr: Expr[Any])(using Quotes): Expr[MockFunction] = 135 | import quotes.reflect.* 136 | 137 | val (obj, name) = transcribeTree(expr.asTerm) 138 | 139 | '{ 140 | if ${ obj.asExprOf[Any] }.isInstanceOf[Mock] 141 | then 142 | ${ obj.asExprOf[Any] } 143 | .asInstanceOf[Mock] 144 | .accessMockFunction(${ Expr(name) }) 145 | else throw Mock.ReceiverIsNotAMock 146 | } 147 | 148 | def transcribeTree(using quotes: Quotes)( 149 | tree: quotes.reflect.Term 150 | ): (quotes.reflect.Term, String) = 151 | import quotes.reflect.* 152 | 153 | tree match 154 | case Inlined(_, _, body) => transcribeTree(body) 155 | case s @ Select(qualifier, name) => 156 | // We don't simply use the qualTpe.classSymbol accessor because we might 157 | // have multiple known type mixed in. At the moment we only support excluding 158 | // the Mock trait from one other base class. The filtering here needs to change 159 | // if (or when) the when macro support union, intersection or with mixin. 160 | // 161 | // dev note: TLDR the filtering is only useful when using `MockImpl.apply` or 162 | // `MockImpl.debug` because those returns `T & Mock`. 163 | val objectSym = Symbol.requiredClass("java.lang.Object") 164 | val anySym = Symbol.requiredClass("scala.Any") 165 | val matchableSym = Symbol.requiredClass("scala.Matchable") 166 | val mockSym = Symbol.requiredClass("context.Mock") 167 | val classSymbol = qualifier.tpe.baseClasses 168 | .filterNot(sym => 169 | sym == objectSym || sym == anySym || sym == matchableSym || sym == mockSym 170 | ) 171 | .headOption 172 | 173 | val n = s.signature match 174 | case None => 175 | // No signature means a nullary function. The mock name will always be the 176 | // same as the function name (no overload possible with nullary) 177 | name 178 | 179 | case Some(signature) => 180 | classSymbol.map(utils.findMethodsToOverride) match 181 | case None => 182 | report.errorAndAbort( 183 | "The when parameter is composed of more than one type, which isn't supported at the moment." 184 | ) 185 | 186 | case Some(methods) => 187 | val overload = 188 | utils.sortSymbolsViaSignature(methods.filter(_.name == name)) 189 | 190 | // If there are no overload, let's use the method name as the mock key. Otherwise 191 | // append the index of the overload. Using the same sort here and in the mock 192 | // declaration is important for the indices to match. 193 | if overload.length < 2 then name 194 | else { 195 | val idx = overload.indexWhere(_.signature == signature) 196 | 197 | s"${name}-$idx" 198 | } 199 | 200 | qualifier -> n 201 | 202 | case Block(stats, _) => 203 | stats 204 | .collectFirst { 205 | case term: Term => transcribeTree(term) 206 | case t: DefDef => 207 | t.rhs match 208 | case None => 209 | report.errorAndAbort( 210 | "Method definition doesn't have a body to investigate. Please report your use case to the maintainers." 211 | ) 212 | case Some(term) => transcribeTree(term) 213 | } 214 | .getOrElse( 215 | report.errorAndAbort( 216 | "Block definition doesn't have a known term. Please report your use case to the maintainers." 217 | ) 218 | ) 219 | 220 | case Apply(fun, _) => transcribeTree(fun) 221 | 222 | case TypeApply(fun, _) => transcribeTree(fun) 223 | case _ => 224 | report.errorAndAbort( 225 | s"ScalaMock: Unrecognised structure: ${tree.show(using Printer.TreeStructure)}." + 226 | "Please open a ticket at https://github.com/fmonniot/scala3mock/issues" 227 | ) 228 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/macros/utils.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.macros 2 | 3 | import scala.quoted.Quotes 4 | 5 | private[macros] object utils: 6 | 7 | def sortSymbolsViaSignature(using quotes: Quotes)( 8 | list: List[quotes.reflect.Symbol] 9 | ): List[quotes.reflect.Symbol] = 10 | import quotes.reflect.* 11 | list.sortBy { sym => 12 | val sig = sym.signature 13 | sig.resultSig + sig.paramSigs.map(_.toString()).mkString 14 | } 15 | 16 | def findMethodsToOverride(using quotes: Quotes)( 17 | sym: quotes.reflect.Symbol 18 | ): List[quotes.reflect.Symbol] = 19 | import quotes.reflect.* 20 | 21 | val objectMembers = Symbol.requiredClass("java.lang.Object").methodMembers 22 | val anyMembers = Symbol.requiredClass("scala.Any").methodMembers 23 | 24 | // First we refine the methods by removing the methods inherited from Object and Any 25 | val candidates = sym.methodMembers 26 | .filter { m => 27 | !(objectMembers.contains(m) || anyMembers.contains(m)) 28 | } 29 | .filterNot(_.flags.is(Flags.Private)) // Do not override private members 30 | 31 | // We then generate a list of methods to ignore for default values. We do this because the 32 | // compiler generate methods (following the `$default$` naming 33 | // scheme) to hold the default value of a parameter (and insert them automatically at call site). 34 | // We do not want to override those. 35 | val namesToIgnore = candidates 36 | .flatMap { sym => 37 | sym.paramSymss 38 | .filterNot(_.exists(_.isType)) 39 | .flatten 40 | .zipWithIndex 41 | .collect { 42 | case (parameter, position) 43 | if parameter.flags.is(Flags.HasDefault) => 44 | s"${sym.name}$$default$$${position + 1}" 45 | } 46 | } 47 | 48 | candidates.filterNot(m => namesToIgnore.contains(m.name)) 49 | end findMethodsToOverride 50 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/matchers/ArgumentMatcher.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.matchers 2 | 3 | class ArgumentMatcher(template: Product) extends (Product => Boolean): 4 | 5 | def apply(args: Product) = template == args 6 | 7 | override def toString = template.productIterator.mkString("(", ", ", ")") 8 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/matchers/MatchAny.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.matchers 2 | 3 | class MatchAny extends MatcherBase: 4 | 5 | override def canEqual(that: Any) = true 6 | 7 | override def equals(that: Any) = true 8 | 9 | override def toString = "*" 10 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/matchers/MatchEpsilon.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.matchers 2 | 3 | import scala.math.abs 4 | 5 | /** Matcher that matches all numbers that are close to a given value */ 6 | class MatchEpsilon(value: Double) extends MatcherBase: 7 | 8 | override def canEqual(that: Any) = that.isInstanceOf[Number] 9 | 10 | override def equals(that: Any) = that match { 11 | case n: Number => abs(value - n.doubleValue) < MatchEpsilon.epsilon 12 | case _ => false 13 | } 14 | 15 | override def toString = "~" + value 16 | 17 | object MatchEpsilon: 18 | val epsilon = 0.001 19 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/matchers/MatchPredicate.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.matchers 2 | 3 | import eu.monniot.scala3mock.functions.* 4 | 5 | // format: off 6 | trait MatchPredicate: 7 | def where[T1](matcher: T1 => Boolean): FunctionAdapter1[T1, Boolean] = 8 | FunctionAdapter1(matcher) 9 | 10 | def where[T1, T2]( 11 | matcher: (T1, T2) => Boolean 12 | ): FunctionAdapter2[T1, T2, Boolean] = FunctionAdapter2(matcher) 13 | 14 | def where[T1, T2, T3]( 15 | matcher: (T1, T2, T3) => Boolean 16 | ): FunctionAdapter3[T1, T2, T3, Boolean] = FunctionAdapter3(matcher) 17 | 18 | def where[T1, T2, T3, T4]( 19 | matcher: (T1, T2, T3, T4) => Boolean 20 | ): FunctionAdapter4[T1, T2, T3, T4, Boolean] = FunctionAdapter4(matcher) 21 | 22 | def where[T1, T2, T3, T4, T5]( 23 | matcher: (T1, T2, T3, T4, T5) => Boolean 24 | ): FunctionAdapter5[T1, T2, T3, T4, T5, Boolean] = FunctionAdapter5(matcher) 25 | 26 | def where[T1, T2, T3, T4, T5, T6]( 27 | matcher: (T1, T2, T3, T4, T5, T6) => Boolean 28 | ): FunctionAdapter6[T1, T2, T3, T4, T5, T6, Boolean] = FunctionAdapter6( 29 | matcher 30 | ) 31 | 32 | def where[T1, T2, T3, T4, T5, T6, T7]( 33 | matcher: (T1, T2, T3, T4, T5, T6, T7) => Boolean 34 | ): FunctionAdapter7[T1, T2, T3, T4, T5, T6, T7, Boolean] = FunctionAdapter7( 35 | matcher 36 | ) 37 | 38 | def where[T1, T2, T3, T4, T5, T6, T7, T8]( 39 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8) => Boolean 40 | ): FunctionAdapter8[T1, T2, T3, T4, T5, T6, T7, T8, Boolean] = 41 | FunctionAdapter8(matcher) 42 | 43 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9]( 44 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9) => Boolean 45 | ): FunctionAdapter9[T1, T2, T3, T4, T5, T6, T7, T8, T9, Boolean] = 46 | FunctionAdapter9(matcher) 47 | 48 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]( 49 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) => Boolean 50 | ): FunctionAdapter10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, Boolean] = 51 | FunctionAdapter10(matcher) 52 | 53 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]( 54 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) => Boolean 55 | ): FunctionAdapter11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, Boolean] = 56 | FunctionAdapter11(matcher) 57 | 58 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]( 59 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) => Boolean 60 | ): FunctionAdapter12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, Boolean] = 61 | FunctionAdapter12(matcher) 62 | 63 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]( 64 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) => Boolean 65 | ): FunctionAdapter13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, Boolean] = 66 | FunctionAdapter13(matcher) 67 | 68 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]( 69 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) => Boolean 70 | ): FunctionAdapter14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, Boolean] = 71 | FunctionAdapter14(matcher) 72 | 73 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]( 74 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) => Boolean 75 | ): FunctionAdapter15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, Boolean] = 76 | FunctionAdapter15(matcher) 77 | 78 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]( 79 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) => Boolean 80 | ): FunctionAdapter16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, Boolean] = 81 | FunctionAdapter16(matcher) 82 | 83 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]( 84 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) => Boolean 85 | ): FunctionAdapter17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, Boolean] = 86 | FunctionAdapter17(matcher) 87 | 88 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]( 89 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) => Boolean 90 | ): FunctionAdapter18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, Boolean] = 91 | FunctionAdapter18(matcher) 92 | 93 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]( 94 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) => Boolean 95 | ): FunctionAdapter19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, Boolean] = 96 | FunctionAdapter19(matcher) 97 | 98 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]( 99 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20) => Boolean 100 | ): FunctionAdapter20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, Boolean] = 101 | FunctionAdapter20(matcher) 102 | 103 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]( 104 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21) => Boolean 105 | ): FunctionAdapter21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, Boolean] = 106 | FunctionAdapter21(matcher) 107 | 108 | def where[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]( 109 | matcher: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22) => Boolean 110 | ): FunctionAdapter22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, Boolean] = 111 | FunctionAdapter22(matcher) 112 | // format: on 113 | object MatchPredicate extends MatchPredicate 114 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/matchers/MatcherBase.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.matchers 2 | 3 | /** Base trait of all ScalaMock argument matchers. 4 | * 5 | * If you want to write a custom matcher please extend the [[Matcher]] trait. 6 | */ 7 | trait MatcherBase extends Equals: 8 | override def toString: String = "Matcher" 9 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/matchers/Matchers.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.matchers 2 | 3 | object Matchers extends Matchers 4 | 5 | trait Matchers extends MatchPredicate: 6 | 7 | def * = MatchAny() 8 | 9 | extension (value: Double) def unary_~ = MatchEpsilon(value) 10 | -------------------------------------------------------------------------------- /core/src/main/scala/eu/monniot/scala3mock/withExpectations.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock 2 | 3 | import eu.monniot.scala3mock.context.{Call, MockContext} 4 | import eu.monniot.scala3mock.functions.MockFunction1 5 | import eu.monniot.scala3mock.handlers.{CallHandler, Handler, UnorderedHandlers} 6 | 7 | import scala.annotation.unused 8 | import scala.collection.mutable.ListBuffer 9 | import scala.util.control.NonFatal 10 | 11 | // A standalone function to run a test with a mock context, asserting all expectations at the end. 12 | def withExpectations[A](verifyAfterRun: Boolean = true)( 13 | f: MockContext ?=> A 14 | ): A = 15 | 16 | val ctx = new MockContext: 17 | override type ExpectationException = MockExpectationFailed 18 | 19 | override def newExpectationException( 20 | message: String, 21 | methodName: Option[String] 22 | ): ExpectationException = 23 | new MockExpectationFailed(message, methodName) 24 | 25 | override def toString() = s"MockContext(callLog = $callLog)" 26 | 27 | def initializeExpectations(): Unit = 28 | val initialHandlers = new UnorderedHandlers 29 | 30 | ctx.callLog = new ListBuffer[Call] 31 | ctx.expectationContext = initialHandlers 32 | 33 | def verifyExpectations(): Unit = 34 | ctx.callLog foreach ctx.expectationContext.verify _ 35 | 36 | val oldCallLog = ctx.callLog 37 | val oldExpectationContext = ctx.expectationContext 38 | 39 | if !oldExpectationContext.isSatisfied then 40 | ctx.reportUnsatisfiedExpectation(oldCallLog, oldExpectationContext) 41 | 42 | try 43 | initializeExpectations() 44 | val result = f(using ctx) 45 | if verifyAfterRun then verifyExpectations() 46 | result 47 | catch 48 | case NonFatal(ex) => 49 | // do not verify expectations - just clear them. Throw original exception 50 | // see issue #72 51 | throw ex 52 | -------------------------------------------------------------------------------- /core/src/test/scala/eu/monniot/scala3mock/features/CallCountSuite.scala: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import eu.monniot.scala3mock.ScalaMocks 4 | import eu.monniot.scala3mock.MockExpectationFailed 5 | 6 | class CallCountSuite extends munit.FunSuite with ScalaMocks { 7 | 8 | test("should fail if an unexpected call is made") { 9 | withExpectations() { 10 | val intFunMock = mockFunction[Int, String] 11 | 12 | intercept[MockExpectationFailed] { intFunMock(42) } 13 | } 14 | } 15 | 16 | test("should fail if a method isn't called often enough (once)") { 17 | withExpectations() { 18 | val noArgFunMock = mockFunction[String] 19 | val intFunMock = mockFunction[Int, String] 20 | 21 | intFunMock.expects(42).once 22 | intFunMock(42) 23 | } 24 | } 25 | 26 | test("should not fail if a method is called once (once)") { 27 | withExpectations() { 28 | val noArgFunMock = mockFunction[String] 29 | val intFunMock = mockFunction[Int, String] 30 | 31 | intFunMock.expects(42).once 32 | intFunMock(42) 33 | } 34 | } 35 | 36 | test("should fail if a method is called too often (once)") { 37 | withExpectations() { 38 | val noArgFunMock = mockFunction[String] 39 | val intFunMock = mockFunction[Int, String] 40 | 41 | intFunMock.expects(42).twice 42 | 43 | intFunMock(42) 44 | intFunMock(42) 45 | intercept[MockExpectationFailed] { intFunMock(42) } 46 | } 47 | } 48 | 49 | test("should fail if a method isn't called often enough (twice)") { 50 | intercept[MockExpectationFailed] { 51 | withExpectations() { 52 | val noArgFunMock = mockFunction[String] 53 | val intFunMock = mockFunction[Int, String] 54 | 55 | intFunMock.expects(42).twice 56 | intFunMock(42) 57 | } 58 | } 59 | } 60 | 61 | test("should fail if a method is called too often (twice)") { 62 | withExpectations() { 63 | val noArgFunMock = mockFunction[String] 64 | val intFunMock = mockFunction[Int, String] 65 | 66 | intFunMock.expects(42).twice 67 | 68 | intFunMock(42) 69 | intFunMock(42) 70 | intercept[MockExpectationFailed] { intFunMock(42) } 71 | } 72 | } 73 | 74 | test("should handle noMoreThanTwice call count (zero)") { 75 | withExpectations() { 76 | val noArgFunMock = mockFunction[String] 77 | val intFunMock = mockFunction[Int, String] 78 | 79 | intFunMock.expects(2).noMoreThanTwice 80 | } 81 | } 82 | 83 | test("should handle noMoreThanTwice call count (one)") { 84 | withExpectations() { 85 | val noArgFunMock = mockFunction[String] 86 | val intFunMock = mockFunction[Int, String] 87 | 88 | intFunMock.expects(2).noMoreThanTwice 89 | intFunMock(2) 90 | } 91 | } 92 | 93 | test("should handle noMoreThanTwice call count (two)") { 94 | withExpectations() { 95 | val noArgFunMock = mockFunction[String] 96 | val intFunMock = mockFunction[Int, String] 97 | 98 | intFunMock.expects(2).noMoreThanTwice 99 | intFunMock(2) 100 | intFunMock(2) 101 | } 102 | } 103 | 104 | test("should handle noMoreThanTwice call count (three)") { 105 | withExpectations() { 106 | val noArgFunMock = mockFunction[String] 107 | val intFunMock = mockFunction[Int, String] 108 | 109 | intFunMock.expects(2).noMoreThanTwice 110 | intFunMock(2) 111 | intFunMock(2) 112 | intercept[MockExpectationFailed] { intFunMock(42) } 113 | } 114 | } 115 | 116 | test("should handle never call count (zero)") { 117 | withExpectations() { 118 | val noArgFunMock = mockFunction[String] 119 | val intFunMock = mockFunction[Int, String] 120 | 121 | intFunMock.expects(2).never 122 | } 123 | } 124 | 125 | test("should handle never call count (one)") { 126 | withExpectations() { 127 | val noArgFunMock = mockFunction[String] 128 | val intFunMock = mockFunction[Int, String] 129 | 130 | intFunMock.expects(2).never 131 | intercept[MockExpectationFailed] { intFunMock(2) } 132 | } 133 | } 134 | 135 | test("should handle exactly(3) call count (3)") { 136 | withExpectations() { 137 | val noArgFunMock = mockFunction[String] 138 | val intFunMock = mockFunction[Int, String] 139 | 140 | intFunMock.expects(2).exactly(3) 141 | 142 | intFunMock(2) 143 | intFunMock(2) 144 | intFunMock(2) 145 | } 146 | } 147 | 148 | test("should handle repeated(1 to 2) call count (0)") { 149 | intercept[MockExpectationFailed] { 150 | withExpectations() { 151 | val intFunMock = mockFunction[Int, String] 152 | 153 | intFunMock.expects(2).repeated(1 to 2) 154 | } 155 | } 156 | } 157 | 158 | test("should handle repeated(1 to 2) call count (1)") { 159 | withExpectations() { 160 | val noArgFunMock = mockFunction[String] 161 | val intFunMock = mockFunction[Int, String] 162 | 163 | intFunMock.expects(2).repeated(1 to 2) 164 | intFunMock(2) 165 | } 166 | } 167 | 168 | test("should handle repeat(1 to 2) call count (2)") { 169 | withExpectations() { 170 | val noArgFunMock = mockFunction[String] 171 | val intFunMock = mockFunction[Int, String] 172 | 173 | intFunMock.expects(2).repeated(1 to 2) 174 | intFunMock(2) 175 | intFunMock(2) 176 | } 177 | } 178 | 179 | test("should handle repeated(1 to 2) call count (3)") { 180 | withExpectations() { 181 | val noArgFunMock = mockFunction[String] 182 | val intFunMock = mockFunction[Int, String] 183 | 184 | intFunMock.expects(2).repeated(1 to 2) 185 | intFunMock(2) 186 | intFunMock(2) 187 | intercept[MockExpectationFailed] { intFunMock(2) } 188 | } 189 | } 190 | 191 | test("should handle exactly(2) call count (2)") { 192 | withExpectations() { 193 | val noArgFunMock = mockFunction[String] 194 | val intFunMock = mockFunction[Int, String] 195 | 196 | intFunMock.expects(2).exactly(2) 197 | intFunMock(2) 198 | intFunMock(2) 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /core/src/test/scala/eu/monniot/scala3mock/features/MatchersSuite.scala: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import eu.monniot.scala3mock.ScalaMocks 4 | 5 | class MatchersSuite extends munit.FunSuite with ScalaMocks { 6 | 7 | test("should let you use star for MatchAny") { 8 | withExpectations() { 9 | val intToStringMock = mockFunction[Int, String] 10 | 11 | intToStringMock.expects(*) 12 | assertEquals(intToStringMock(5), null) 13 | } 14 | } 15 | 16 | test("should let you use tilde for MatchEpsilon") { 17 | withExpectations() { 18 | val intToStringMock = mockFunction[Double, String] 19 | 20 | intToStringMock.expects(~2.0) 21 | assertEquals(intToStringMock(2.001), null) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/src/test/scala/eu/monniot/scala3mock/features/ReturnSuite.scala: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import eu.monniot.scala3mock.ScalaMocks 4 | 5 | class ReturnSuite extends munit.FunSuite with ScalaMocks { 6 | 7 | test("should return null by default") { 8 | withExpectations() { 9 | val intToStringMock = mockFunction[Int, String] 10 | val intToIntMock = mockFunction[Int, Int] 11 | 12 | intToStringMock.expects(*) 13 | assertEquals(intToStringMock(5), null) 14 | } 15 | } 16 | 17 | test("should return a null-like default value for non reference types") { 18 | withExpectations() { 19 | val intToStringMock = mockFunction[Int, String] 20 | val intToIntMock = mockFunction[Int, Int] 21 | 22 | intToIntMock.expects(*) 23 | assertEquals(intToIntMock(5), 0) 24 | } 25 | } 26 | 27 | test("should return what they're told to") { 28 | withExpectations() { 29 | val intToStringMock = mockFunction[Int, String] 30 | val intToIntMock = mockFunction[Int, Int] 31 | 32 | intToStringMock.expects(*).returning("a return value") 33 | assertEquals(intToStringMock(5), "a return value") 34 | } 35 | } 36 | 37 | test("should return a calculated return value") { 38 | withExpectations() { 39 | val intToStringMock = mockFunction[Int, String] 40 | val intToIntMock = mockFunction[Int, Int] 41 | 42 | intToIntMock.expects(*).onCall({ (arg) => arg + 1 }) 43 | assertEquals(intToIntMock(42), 43) 44 | } 45 | } 46 | 47 | test("should return a calculated return value (unary function)") { 48 | withExpectations() { 49 | val intToIntMock = mockFunction[Int] 50 | 51 | intToIntMock.expects().onCall({ () => 44 }) 52 | assertEquals(intToIntMock(), 44) 53 | } 54 | } 55 | 56 | test("should return a calculated return value (chaining mocks)") { 57 | withExpectations() { 58 | val m1 = mockFunction[Int, String] 59 | val m2 = mockFunction[Int, String] 60 | 61 | m1.expects(42).onCall(m2) 62 | m2.expects(42).returning("a return value") 63 | 64 | assertEquals(m1(42), "a return value") 65 | } 66 | } 67 | 68 | test("should handle stacked expectations (returning)") { 69 | withExpectations() { 70 | val intToStringMock = mockFunction[Int, String] 71 | 72 | intToStringMock.expects(*).returning("1") 73 | intToStringMock.expects(*).returning("2") 74 | 75 | assertEquals(intToStringMock(1), "1") 76 | assertEquals(intToStringMock(2), "2") 77 | } 78 | } 79 | 80 | test("should handle stacked expectations (returning) and call count") { 81 | withExpectations() { 82 | val intToStringMock = mockFunction[Int, String] 83 | val intToIntMock = mockFunction[Int, Int] 84 | 85 | intToStringMock.expects(*).returning("1").twice 86 | intToStringMock.expects(*).returning("2") 87 | 88 | assertEquals(intToStringMock(1), "1") 89 | assertEquals(intToStringMock(1), "1") 90 | assertEquals(intToStringMock(1), "2") 91 | } 92 | } 93 | 94 | test("should handle stacked expectations (onCall)") { 95 | withExpectations() { 96 | val intToStringMock = mockFunction[Int, String] 97 | val intToIntMock = mockFunction[Int, Int] 98 | 99 | intToStringMock.expects(*).onCall((_) => "1") 100 | intToStringMock.expects(*).onCall((_) => "2") 101 | 102 | assertEquals(intToStringMock(1), "1") 103 | assertEquals(intToStringMock(1), "2") 104 | } 105 | } 106 | 107 | test("should handle stacked expectations (onCall) and call count") { 108 | withExpectations() { 109 | val intToStringMock = mockFunction[Int, String] 110 | 111 | intToStringMock.expects(*).onCall((_) => "1").twice 112 | intToStringMock.expects(*).onCall((_) => "2") 113 | 114 | assertEquals(intToStringMock(1), "1") 115 | assertEquals(intToStringMock(1), "1") 116 | assertEquals(intToStringMock(1), "2") 117 | } 118 | } 119 | 120 | test("should match return value to provided arguments (onCall)") { 121 | withExpectations() { 122 | val intToStringMock = mockFunction[Int, String] 123 | 124 | intToStringMock.expects(1).onCall({ (_) => "1" }) 125 | intToStringMock.expects(2).onCall({ (_) => "2" }) 126 | 127 | assertEquals(intToStringMock(2), "2") 128 | assertEquals(intToStringMock(1), "1") 129 | } 130 | } 131 | 132 | test("should match return value to provided arguments (same order)") { 133 | withExpectations() { 134 | val intToStringMock = mockFunction[Int, String] 135 | 136 | intToStringMock.expects(1).returning("1") 137 | intToStringMock.expects(2).returning("2") 138 | 139 | assertEquals(intToStringMock(1), "1") 140 | assertEquals(intToStringMock(2), "2") 141 | } 142 | } 143 | 144 | test("should match return value to provided arguments (different order)") { 145 | withExpectations() { 146 | val intToStringMock = mockFunction[Int, String] 147 | 148 | intToStringMock.expects(1).returning("1") 149 | intToStringMock.expects(2).returning("2") 150 | 151 | assertEquals(intToStringMock(2), "2") 152 | assertEquals(intToStringMock(1), "1") 153 | } 154 | } 155 | 156 | test("should match return value to provided arguments (call count)") { 157 | withExpectations() { 158 | val intToStringMock = mockFunction[Int, String] 159 | 160 | intToStringMock.expects(1).returning("1").twice 161 | intToStringMock.expects(2).returning("2") 162 | 163 | assertEquals(intToStringMock(1), "1") 164 | assertEquals(intToStringMock(2), "2") 165 | assertEquals(intToStringMock(1), "1") 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /core/src/test/scala/eu/monniot/scala3mock/features/ThrowSuite.scala: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import eu.monniot.scala3mock.ScalaMocks 4 | 5 | class ThrowSuite extends munit.FunSuite with ScalaMocks { 6 | 7 | case class TestException() extends RuntimeException 8 | case class AnotherTestException() extends RuntimeException 9 | 10 | test("should throw what it is told to (throwing)") { 11 | withExpectations() { 12 | val noArgFunMock = mockFunction[String] 13 | 14 | noArgFunMock.expects().throwing(new TestException) 15 | intercept[TestException] { noArgFunMock() } 16 | } 17 | } 18 | 19 | test("throw what it is told to (throws)") { 20 | withExpectations() { 21 | val noArgFunMock = mockFunction[String] 22 | 23 | noArgFunMock.expects().throws(new TestException) 24 | intercept[TestException] { noArgFunMock() } 25 | } 26 | } 27 | 28 | test("throw computed exception") { 29 | withExpectations() { 30 | val intFunMock = mockFunction[Int, String] 31 | 32 | intFunMock 33 | .expects(*) 34 | .exactly(3) 35 | .onCall({ (arg) => 36 | if (arg == 1) throw new TestException() 37 | else if (arg == 2) throw new AnotherTestException() 38 | else "Foo" 39 | }) 40 | 41 | intercept[TestException] { intFunMock(1) } 42 | intercept[AnotherTestException] { intFunMock(2) } 43 | assertEquals(intFunMock(3), "Foo") 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/ClassWithoutTypeParameters.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | class ClassWithoutTypeParameters(i: Int, s: String) 4 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/ContextBound.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | trait ContextBound[T] {} 4 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/ContextBoundInheritance.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | import eu.monniot.scala3mock.context.MockContext 4 | import eu.monniot.scala3mock.macros.mock 5 | 6 | trait ContextBoundInheritance[F[_]: Applicative] 7 | 8 | trait ContextBoundInheritanceChild[F[_]: App2] 9 | extends ContextBoundInheritance[F] 10 | 11 | trait App2[G[_]] extends Applicative[G] 12 | object App2 { 13 | given App2[List] = new App2[List]: 14 | override def pure[A](a: A): List[A] = List(a) 15 | } 16 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/HigherOrderPolymorphicTrait.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | trait HigherOrderPolymorphicTrait[F[_]] { 4 | 5 | def doSomething(param: Int): F[Boolean] 6 | 7 | } 8 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/ManyParamsClass.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | // format: off 4 | class ManyParamsClass { 5 | 6 | def methodWith1Ints(v1: Int) = 7 | v1 8 | 9 | def methodWith2Ints(v1: Int, v2: Int) = 10 | v1 + v2 11 | 12 | def methodWith3Ints(v1: Int, v2: Int, v3: Int) = 13 | v1 + v2 + v3 14 | 15 | def methodWith4Ints(v1: Int, v2: Int, v3: Int, v4: Int) = 16 | v1 + v2 + v3 + v4 17 | 18 | def methodWith5Ints(v1: Int, v2: Int, v3: Int, v4: Int, v5: Int) = 19 | v1 + v2 + v3 + v4 + v5 20 | 21 | def methodWith6Ints(v1: Int, v2: Int, v3: Int, v4: Int, v5: Int, v6: Int) = 22 | v1 + v2 + v3 + v4 + v5 + v6 23 | 24 | def methodWith7Ints( 25 | v1: Int, 26 | v2: Int, 27 | v3: Int, 28 | v4: Int, 29 | v5: Int, 30 | v6: Int, 31 | v7: Int 32 | ) = 33 | v1 + v2 + v3 + v4 + v5 + v6 + v7 34 | 35 | def methodWith8Ints( 36 | v1: Int, 37 | v2: Int, 38 | v3: Int, 39 | v4: Int, 40 | v5: Int, 41 | v6: Int, 42 | v7: Int, 43 | v8: Int 44 | ) = 45 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 46 | 47 | def methodWith9Ints( 48 | v1: Int, 49 | v2: Int, 50 | v3: Int, 51 | v4: Int, 52 | v5: Int, 53 | v6: Int, 54 | v7: Int, 55 | v8: Int, 56 | v9: Int 57 | ) = 58 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 59 | 60 | def methodWith10Ints( 61 | v1: Int, 62 | v2: Int, 63 | v3: Int, 64 | v4: Int, 65 | v5: Int, 66 | v6: Int, 67 | v7: Int, 68 | v8: Int, 69 | v9: Int, 70 | v10: Int 71 | ) = 72 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 73 | 74 | def methodWith11Ints( 75 | v1: Int, 76 | v2: Int, 77 | v3: Int, 78 | v4: Int, 79 | v5: Int, 80 | v6: Int, 81 | v7: Int, 82 | v8: Int, 83 | v9: Int, 84 | v10: Int, 85 | v11: Int, 86 | ) = 87 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 88 | 89 | def methodWith12Ints( 90 | v1: Int, 91 | v2: Int, 92 | v3: Int, 93 | v4: Int, 94 | v5: Int, 95 | v6: Int, 96 | v7: Int, 97 | v8: Int, 98 | v9: Int, 99 | v10: Int, 100 | v11: Int, 101 | v12: Int, 102 | ) = 103 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 104 | 105 | def methodWith13Ints( 106 | v1: Int, 107 | v2: Int, 108 | v3: Int, 109 | v4: Int, 110 | v5: Int, 111 | v6: Int, 112 | v7: Int, 113 | v8: Int, 114 | v9: Int, 115 | v10: Int, 116 | v11: Int, 117 | v12: Int, 118 | v13: Int, 119 | ) = 120 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 + v13 121 | 122 | def methodWith14Ints( 123 | v1: Int, 124 | v2: Int, 125 | v3: Int, 126 | v4: Int, 127 | v5: Int, 128 | v6: Int, 129 | v7: Int, 130 | v8: Int, 131 | v9: Int, 132 | v10: Int, 133 | v11: Int, 134 | v12: Int, 135 | v13: Int, 136 | v14: Int, 137 | ) = 138 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 + v13 + v14 139 | 140 | def methodWith15Ints( 141 | v1: Int, 142 | v2: Int, 143 | v3: Int, 144 | v4: Int, 145 | v5: Int, 146 | v6: Int, 147 | v7: Int, 148 | v8: Int, 149 | v9: Int, 150 | v10: Int, 151 | v11: Int, 152 | v12: Int, 153 | v13: Int, 154 | v14: Int, 155 | v15: Int, 156 | ) = 157 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 + v13 + v14 + v15 158 | 159 | def methodWith16Ints( 160 | v1: Int, 161 | v2: Int, 162 | v3: Int, 163 | v4: Int, 164 | v5: Int, 165 | v6: Int, 166 | v7: Int, 167 | v8: Int, 168 | v9: Int, 169 | v10: Int, 170 | v11: Int, 171 | v12: Int, 172 | v13: Int, 173 | v14: Int, 174 | v15: Int, 175 | v16: Int, 176 | ) = 177 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 + v13 + v14 + v15 + v16 178 | 179 | def methodWith17Ints( 180 | v1: Int, 181 | v2: Int, 182 | v3: Int, 183 | v4: Int, 184 | v5: Int, 185 | v6: Int, 186 | v7: Int, 187 | v8: Int, 188 | v9: Int, 189 | v10: Int, 190 | v11: Int, 191 | v12: Int, 192 | v13: Int, 193 | v14: Int, 194 | v15: Int, 195 | v16: Int, 196 | v17: Int, 197 | ) = 198 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 + v13 + v14 + v15 + v16 + v17 199 | 200 | def methodWith18Ints( 201 | v1: Int, 202 | v2: Int, 203 | v3: Int, 204 | v4: Int, 205 | v5: Int, 206 | v6: Int, 207 | v7: Int, 208 | v8: Int, 209 | v9: Int, 210 | v10: Int, 211 | v11: Int, 212 | v12: Int, 213 | v13: Int, 214 | v14: Int, 215 | v15: Int, 216 | v16: Int, 217 | v17: Int, 218 | v18: Int, 219 | ) = 220 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 + v13 + v14 + v15 + v16 + v17 + v18 221 | 222 | def methodWith19Ints( 223 | v1: Int, 224 | v2: Int, 225 | v3: Int, 226 | v4: Int, 227 | v5: Int, 228 | v6: Int, 229 | v7: Int, 230 | v8: Int, 231 | v9: Int, 232 | v10: Int, 233 | v11: Int, 234 | v12: Int, 235 | v13: Int, 236 | v14: Int, 237 | v15: Int, 238 | v16: Int, 239 | v17: Int, 240 | v18: Int, 241 | v19: Int, 242 | ) = 243 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 + v13 + v14 + v15 + v16 + v17 + v18 + v19 244 | 245 | def methodWith20Ints( 246 | v1: Int, 247 | v2: Int, 248 | v3: Int, 249 | v4: Int, 250 | v5: Int, 251 | v6: Int, 252 | v7: Int, 253 | v8: Int, 254 | v9: Int, 255 | v10: Int, 256 | v11: Int, 257 | v12: Int, 258 | v13: Int, 259 | v14: Int, 260 | v15: Int, 261 | v16: Int, 262 | v17: Int, 263 | v18: Int, 264 | v19: Int, 265 | v20: Int, 266 | ) = 267 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 + v13 + v14 + v15 + v16 + v17 + v18 + v19 + v20 268 | 269 | def methodWith21Ints( 270 | v1: Int, 271 | v2: Int, 272 | v3: Int, 273 | v4: Int, 274 | v5: Int, 275 | v6: Int, 276 | v7: Int, 277 | v8: Int, 278 | v9: Int, 279 | v10: Int, 280 | v11: Int, 281 | v12: Int, 282 | v13: Int, 283 | v14: Int, 284 | v15: Int, 285 | v16: Int, 286 | v17: Int, 287 | v18: Int, 288 | v19: Int, 289 | v20: Int, 290 | v21: Int, 291 | ) = 292 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 + v13 + v14 + v15 + v16 + v17 + v18 + v19 + v20 + v21 293 | 294 | def methodWith22Ints( 295 | v1: Int, 296 | v2: Int, 297 | v3: Int, 298 | v4: Int, 299 | v5: Int, 300 | v6: Int, 301 | v7: Int, 302 | v8: Int, 303 | v9: Int, 304 | v10: Int, 305 | v11: Int, 306 | v12: Int, 307 | v13: Int, 308 | v14: Int, 309 | v15: Int, 310 | v16: Int, 311 | v17: Int, 312 | v18: Int, 313 | v19: Int, 314 | v20: Int, 315 | v21: Int, 316 | v22: Int, 317 | ) = 318 | v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 + v13 + v14 + v15 + v16 + v17 + v18 + v19 + v20 + v21 + v22 319 | } 320 | // format: on 321 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/ManyParamsTrait.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | // format: off 4 | trait ManyParamsTrait { 5 | 6 | def methodWith1Ints(v1: Int): Int 7 | def methodWith2Ints(v1: Int, v2: Int): Int 8 | def methodWith3Ints(v1: Int, v2: Int, v3: Int): Int 9 | def methodWith4Ints(v1: Int, v2: Int, v3: Int, v4: Int): Int 10 | def methodWith5Ints(v1: Int, v2: Int, v3: Int, v4: Int, v5: Int): Int 11 | def methodWith6Ints(v1: Int, v2: Int, v3: Int, v4: Int, v5: Int, v6: Int): Int 12 | def methodWith7Ints( 13 | v1: Int, 14 | v2: Int, 15 | v3: Int, 16 | v4: Int, 17 | v5: Int, 18 | v6: Int, 19 | v7: Int 20 | ): Int 21 | def methodWith8Ints( 22 | v1: Int, 23 | v2: Int, 24 | v3: Int, 25 | v4: Int, 26 | v5: Int, 27 | v6: Int, 28 | v7: Int, 29 | v8: Int 30 | ): Int 31 | def methodWith9Ints( 32 | v1: Int, 33 | v2: Int, 34 | v3: Int, 35 | v4: Int, 36 | v5: Int, 37 | v6: Int, 38 | v7: Int, 39 | v8: Int, 40 | v9: Int 41 | ): Int 42 | def methodWith10Ints( 43 | v1: Int, 44 | v2: Int, 45 | v3: Int, 46 | v4: Int, 47 | v5: Int, 48 | v6: Int, 49 | v7: Int, 50 | v8: Int, 51 | v9: Int, 52 | v10: Int 53 | ): Int 54 | def methodWith11Ints( 55 | v1: Int, 56 | v2: Int, 57 | v3: Int, 58 | v4: Int, 59 | v5: Int, 60 | v6: Int, 61 | v7: Int, 62 | v8: Int, 63 | v9: Int, 64 | v10: Int, 65 | v11: Int 66 | ): Int 67 | def methodWith12Ints( 68 | v1: Int, 69 | v2: Int, 70 | v3: Int, 71 | v4: Int, 72 | v5: Int, 73 | v6: Int, 74 | v7: Int, 75 | v8: Int, 76 | v9: Int, 77 | v10: Int, 78 | v11: Int, 79 | v12: Int 80 | ): Int 81 | def methodWith13Ints( 82 | v1: Int, 83 | v2: Int, 84 | v3: Int, 85 | v4: Int, 86 | v5: Int, 87 | v6: Int, 88 | v7: Int, 89 | v8: Int, 90 | v9: Int, 91 | v10: Int, 92 | v11: Int, 93 | v12: Int, 94 | v13: Int, 95 | ): Int 96 | def methodWith14Ints( 97 | v1: Int, 98 | v2: Int, 99 | v3: Int, 100 | v4: Int, 101 | v5: Int, 102 | v6: Int, 103 | v7: Int, 104 | v8: Int, 105 | v9: Int, 106 | v10: Int, 107 | v11: Int, 108 | v12: Int, 109 | v13: Int, 110 | v14: Int, 111 | ): Int 112 | def methodWith15Ints( 113 | v1: Int, 114 | v2: Int, 115 | v3: Int, 116 | v4: Int, 117 | v5: Int, 118 | v6: Int, 119 | v7: Int, 120 | v8: Int, 121 | v9: Int, 122 | v10: Int, 123 | v11: Int, 124 | v12: Int, 125 | v13: Int, 126 | v14: Int, 127 | v15: Int, 128 | ): Int 129 | def methodWith16Ints( 130 | v1: Int, 131 | v2: Int, 132 | v3: Int, 133 | v4: Int, 134 | v5: Int, 135 | v6: Int, 136 | v7: Int, 137 | v8: Int, 138 | v9: Int, 139 | v10: Int, 140 | v11: Int, 141 | v12: Int, 142 | v13: Int, 143 | v14: Int, 144 | v15: Int, 145 | v16: Int, 146 | ): Int 147 | def methodWith17Ints( 148 | v1: Int, 149 | v2: Int, 150 | v3: Int, 151 | v4: Int, 152 | v5: Int, 153 | v6: Int, 154 | v7: Int, 155 | v8: Int, 156 | v9: Int, 157 | v10: Int, 158 | v11: Int, 159 | v12: Int, 160 | v13: Int, 161 | v14: Int, 162 | v15: Int, 163 | v16: Int, 164 | v17: Int, 165 | ): Int 166 | def methodWith18Ints( 167 | v1: Int, 168 | v2: Int, 169 | v3: Int, 170 | v4: Int, 171 | v5: Int, 172 | v6: Int, 173 | v7: Int, 174 | v8: Int, 175 | v9: Int, 176 | v10: Int, 177 | v11: Int, 178 | v12: Int, 179 | v13: Int, 180 | v14: Int, 181 | v15: Int, 182 | v16: Int, 183 | v17: Int, 184 | v18: Int, 185 | ): Int 186 | def methodWith19Ints( 187 | v1: Int, 188 | v2: Int, 189 | v3: Int, 190 | v4: Int, 191 | v5: Int, 192 | v6: Int, 193 | v7: Int, 194 | v8: Int, 195 | v9: Int, 196 | v10: Int, 197 | v11: Int, 198 | v12: Int, 199 | v13: Int, 200 | v14: Int, 201 | v15: Int, 202 | v16: Int, 203 | v17: Int, 204 | v18: Int, 205 | v19: Int, 206 | ): Int 207 | def methodWith20Ints( 208 | v1: Int, 209 | v2: Int, 210 | v3: Int, 211 | v4: Int, 212 | v5: Int, 213 | v6: Int, 214 | v7: Int, 215 | v8: Int, 216 | v9: Int, 217 | v10: Int, 218 | v11: Int, 219 | v12: Int, 220 | v13: Int, 221 | v14: Int, 222 | v15: Int, 223 | v16: Int, 224 | v17: Int, 225 | v18: Int, 226 | v19: Int, 227 | v20: Int, 228 | ): Int 229 | def methodWith21Ints( 230 | v1: Int, 231 | v2: Int, 232 | v3: Int, 233 | v4: Int, 234 | v5: Int, 235 | v6: Int, 236 | v7: Int, 237 | v8: Int, 238 | v9: Int, 239 | v10: Int, 240 | v11: Int, 241 | v12: Int, 242 | v13: Int, 243 | v14: Int, 244 | v15: Int, 245 | v16: Int, 246 | v17: Int, 247 | v18: Int, 248 | v19: Int, 249 | v20: Int, 250 | v21: Int, 251 | ): Int 252 | def methodWith22Ints( 253 | v1: Int, 254 | v2: Int, 255 | v3: Int, 256 | v4: Int, 257 | v5: Int, 258 | v6: Int, 259 | v7: Int, 260 | v8: Int, 261 | v9: Int, 262 | v10: Int, 263 | v11: Int, 264 | v12: Int, 265 | v13: Int, 266 | v14: Int, 267 | v15: Int, 268 | v16: Int, 269 | v17: Int, 270 | v18: Int, 271 | v19: Int, 272 | v20: Int, 273 | v21: Int, 274 | v22: Int, 275 | ): Int 276 | } 277 | // format: on 278 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/PolymorphicClassWithParameters.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | class PolymorphicClassWithParameters[F[_], E]( 4 | a: HigherOrderPolymorphicTrait[F], 5 | n: Int 6 | )(b: String) { 7 | 8 | def logic(stuff: String): F[Boolean] = a.doSomething(stuff.toInt) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/PolymorphicTrait.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | trait PolymorphicTrait[T] { 4 | def method[U](x: Int, y: T, z: U): T 5 | 6 | trait Embedded[V] { 7 | trait ATrait[A, B] 8 | 9 | def innerTrait(t: T, v: V): ATrait[T, V] 10 | def outerTrait(t: T, v: V): PolymorphicTrait.this.ATrait[T, V] 11 | } 12 | 13 | trait ATrait[A, B] 14 | 15 | // def referenceEmbedded(): Embedded 16 | } 17 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/SpecializedClass.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | class SpecializedClass[@specialized T] { 4 | def identity(x: T) = x 5 | } 6 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/SpecializedClass2.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | class SpecializedClass2[@specialized T1, @specialized T2] 4 | extends SpecializedClass[T1] { 5 | def identity2(x: T1, y: T2) = (x, y) 6 | } 7 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/TestClass.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | // Not the purest form, but who really care in a test ? 4 | trait Applicative[F[_]] { 5 | def pure[A](a: A): F[A] 6 | } 7 | 8 | object Applicative { 9 | def apply[F[_]](using f: Applicative[F]) = f 10 | given Applicative[List] = new Applicative[List]: 11 | override def pure[A](a: A): List[A] = List(a) 12 | } 13 | 14 | // This test case is representative of a common implementation class in the pure 15 | // functional programming world. I think. 16 | class TestClass[F[_]: Applicative](dep: String)(dep2: Int)(using User) { 17 | private def pm(x: Int) = x 18 | 19 | def a(x: Int, y: String): (Int, String) = x -> y 20 | def b(x: Int): F[Int] = Applicative[F].pure(x) 21 | } 22 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/TestDefaultParameters.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | trait TestDefaultParameters { 4 | def foo(bar: Int = 9): String 5 | def foo2(b: Int = 1, c: Long): String 6 | 7 | def defaultAfterRegular(a: Int, b: Int = 0): String 8 | def multiParamList(a: Int, b: Int = 0)(c: Long, d: Long = 0): String 9 | 10 | def withTypeParam[A, B](a: A, b: B)(c: Long, d: Long = 0): String 11 | } 12 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/TestTrait.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | import some.other.pkg.SomeClass 4 | 5 | trait TestTrait { 6 | 7 | def nullary: String 8 | def noParams(): String 9 | def oneParam(x: Int): String 10 | def twoParams(x: Int, y: Double): String 11 | 12 | def overloaded(x: Int): String 13 | def overloaded(x: String): String 14 | def overloaded(x: Int, y: Double): String 15 | def overloaded[T](x: T): String 16 | 17 | def +(x: TestTrait): TestTrait 18 | 19 | def curried(x: Int)(y: Double): String 20 | def curriedFuncReturn(x: Int): Double => String 21 | 22 | def polymorphic[T](x: List[T]): String 23 | def polymorphicUnary[T1]: T1 24 | def polycurried[T1, T2](x: T1)(y: T2): (T1, T2) 25 | def polymorphicParam(x: (Int, Double)): String 26 | def repeatedParam(x: Int, ys: String*): String 27 | def byNameParam(x: => Int): String 28 | 29 | def implicitParam(x: Int)(implicit y: Double): String 30 | def usingParam(x: Int)(using y: Double): String 31 | def contextBound[T: ContextBound](x: T): String 32 | 33 | def upperBound[T <: Product](x: T): Int 34 | def lowerBound[T >: U, U](x: T, y: List[U]): String 35 | 36 | def withImplementation(x: Int) = x * x 37 | 38 | def referencesSomeOtherPackage(x: SomeClass): SomeClass 39 | def otherPackageUpperBound[T <: SomeClass](x: T): T 40 | def explicitPackageReference( 41 | x: yet.another.pkg.YetAnotherClass 42 | ): yet.another.pkg.YetAnotherClass 43 | def explicitPackageUpperBound[T <: yet.another.pkg.YetAnotherClass](x: T): T 44 | 45 | // doesn't seems to be possible with Scala 3. 46 | // We get an error saying "error overriding variable [...] cannot override a mutable variable" 47 | // var aVar: String 48 | // var concreteVar = "foo" 49 | 50 | // Here all we can do is making those non-abstract so the mock can compile. 51 | // By definition, there is no way to dynamically change the value of a val. 52 | val aVal: String 53 | val concreteVal = "foo" 54 | // val fnVal: String => Int 55 | } 56 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/UserDatabase.scala: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | case class User(name: String, age: Int) 4 | case class Address(city: String, street: String) 5 | 6 | trait UserDatabase { 7 | def storeUser(user: User): String 8 | def addUserAddress(user: User, address: Address): String 9 | } 10 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/some/other/pkg/SomeClass.scala: -------------------------------------------------------------------------------- 1 | package fixtures.some.other.pkg 2 | 3 | class SomeClass { 4 | def m(x: Int) = x.toString 5 | } 6 | -------------------------------------------------------------------------------- /core/src/test/scala/fixtures/yet/another/pkg/YetAnotherClass.scala: -------------------------------------------------------------------------------- 1 | package fixtures.yet.another.pkg 2 | 3 | class YetAnotherClass { 4 | def m(x: Int) = x.toString 5 | } 6 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: getting-started 3 | title: Getting Started 4 | --- 5 | 6 | This article describes how to get started with Scala3Mock. Because it is only an introduction, only the basics usage are described. For a comprehensive guide, see the [User Guide](user-guide/features.md). 7 | 8 | If you are coming from ScalaMock for Scala 2, there are a few changes you need to be aware of. See the [FAQ](user-guide/faq.md#moving-from-scalamock-to-scala3mock). 9 | 10 | ## Install 11 | 12 | To get started with SBT and ScalaTest, add the following dependencies to your **build.sbt**: 13 | ```scala 14 | libraryDependencies += "eu.monniot" %% "scala3mock" % "@VERSION@" % Test 15 | libraryDependencies += "eu.monniot" %% "scala3mock-scalatest" % "@VERSION@" % Test 16 | ``` 17 | 18 | While some testing framework integration exists, Scala3Mock at its core do not require one. 19 | You can learn more about the various testing framework integration by going to to the dedicated page in the user guide. If there is no page, it means no special integration has been written yet (or it is not required). 20 | 21 | 22 | 23 | ## Basic Usage 24 | 25 | > :info: The scala code you'll see in this example is being compiled and should work as is. Note that a snippet may depend on previous snippets. 26 | 27 | First let's assume we have some functionality to test. We are going to be very imaginative and use a `Greeting` example. To simplify the implementation, it will have a `sayHello` function which take a name and a formatter, and print the formatted greeting. 28 | 29 | ```scala mdoc 30 | object Greetings { 31 | sealed trait Formatter { def format(s: String): String } 32 | object English extends Formatter { def format(s: String) = s"Hello $s" } 33 | object French extends Formatter { def format(s: String) = s"Bonjour $s" } 34 | object Japanese extends Formatter { def format(s: String) = s"こんにちは $s" } 35 | 36 | def sayHello(name: String, formatter: Formatter): Unit = 37 | println(formatter.format(name)) 38 | } 39 | 40 | // Let's import the content of our object for future snippets 41 | import Greetings.* 42 | ``` 43 | 44 | With Scala3Mock, we can test the interaction between the `sayHello` function and a given `Formatter`. To be able to mock things, we need an implicit `MockContext`. The core library provides a `withExpectations` function that provide you with one. 45 | 46 | For example, we can check that the name is actually being used and that our formatter is called only once. 47 | 48 | To create a new mock, place the following contents into a Scala file within your project: 49 | 50 | ```scala mdoc 51 | import eu.monniot.scala3mock.ScalaMocks.* 52 | 53 | withExpectations() { 54 | val formatter = mock[Formatter] 55 | 56 | when(formatter.format) 57 | .expects("Mr Bond") 58 | .returns("Ah, Mr Bond. I've been expecting you") 59 | .once 60 | 61 | sayHello("Mr Bond", formatter) 62 | } 63 | ``` 64 | 65 | Note that you do not have to specify the argument specifically but can instead accept any arguments to a mock with `AnyMatcher` or its `*` alias. 66 | 67 | ```scala mdoc 68 | withExpectations() { 69 | val formatter = mock[Formatter] 70 | 71 | when(formatter.format) 72 | .expects(*) 73 | .returns("Ah, Mr Bond. I've been expecting you") 74 | .once 75 | 76 | sayHello("Mr Bond", formatter) 77 | } 78 | ``` 79 | 80 | Other basic available features that you may find useful are included below. 81 | 82 | ## Throwing an exception in a mock 83 | 84 | ```scala mdoc 85 | withExpectations() { 86 | val brokenFormatter = mock[Formatter] 87 | 88 | when(brokenFormatter.format) 89 | .expects(*) 90 | .throwing(new NullPointerException) 91 | .anyNumberOfTimes 92 | 93 | try Greetings.sayHello("Erza", brokenFormatter) 94 | catch { 95 | case e: NullPointerException => println("expected") 96 | } 97 | } 98 | ``` 99 | 100 | ## Dynamic return value 101 | 102 | ```scala mdoc 103 | withExpectations() { 104 | val australianFormat = mock[Formatter] 105 | 106 | when(australianFormat.format) 107 | .expects(*) 108 | .onCall { (s: String) => s"G'day $s" } 109 | .twice 110 | 111 | Greetings.sayHello("Wendy", australianFormat) 112 | Greetings.sayHello("Gray", australianFormat) 113 | } 114 | ``` 115 | 116 | ## Verifying arguments dynamically 117 | 118 | ```scala mdoc 119 | withExpectations() { 120 | val teamNatsu = Set("Natsu", "Lucy", "Happy", "Erza", "Gray", "Wendy", "Carla") 121 | val formatter = mock[Formatter] 122 | 123 | def assertTeamNatsu(s: String): Unit = { 124 | assert(teamNatsu.contains(s)) 125 | } 126 | 127 | // 'where' verifies at the end of the test 128 | when(formatter.format) 129 | .expects(where { (s: String) => teamNatsu contains(s) }) 130 | .onCall { (s: String) => s"Yo $s" } 131 | .twice 132 | 133 | Greetings.sayHello("Carla", formatter) 134 | Greetings.sayHello("Lucy", formatter) 135 | } 136 | ``` 137 | 138 | ## Further reading 139 | 140 | Scala3Mock has some powerful features. The example below show some of them. 141 | 142 | ```scala mdoc:invisible 143 | sealed trait Method 144 | object Method { 145 | case object GET extends Method 146 | case object POST extends Method 147 | } 148 | sealed trait Http 149 | object Http { 150 | case object NotFound extends Http 151 | } 152 | class HttpClient() { 153 | def sendRequest(m: Method, url: String, body: String): Http = ??? 154 | } 155 | class Counter { 156 | def increment(i: Int): Int = ??? 157 | def decrement: Int = ??? 158 | } 159 | ``` 160 | 161 | ```scala mdoc 162 | withExpectations(verifyAfterRun=false) { 163 | val httpClient = mock[HttpClient] 164 | val counterMock = mock[Counter] 165 | 166 | when(httpClient.sendRequest).expects(Method.GET, *, *).twice 167 | when(httpClient.sendRequest).expects(Method.POST, "http://scalamock.org", *).noMoreThanOnce 168 | when(httpClient.sendRequest).expects(Method.POST, "http://example.com", *).returning(Http.NotFound) 169 | when(counterMock.increment).expects(*).onCall { (arg: Int) => arg + 1} 170 | when(() => counterMock.decrement).expects().onCall { () => throw RuntimeException("here") } 171 | } 172 | ``` 173 | 174 | Please read the [User Guide](user-guide/features.md) for more details. 175 | -------------------------------------------------------------------------------- /docs/user-guide/advanced-topics.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advanced Topics 3 | --- 4 | 5 | ```scala mdoc:invisible 6 | // Make shift mock context so that we can call `mock` without constraint. 7 | // Don't do that in your test cases folks :) 8 | given eu.monniot.scala3mock.context.MockContext = 9 | new eu.monniot.scala3mock.context.MockContext: 10 | override type ExpectationException = eu.monniot.scala3mock.MockExpectationFailed 11 | 12 | override def newExpectationException(message: String, methodName: Option[String]): ExpectationException = 13 | eu.monniot.scala3mock.MockExpectationFailed(message, methodName) 14 | 15 | override def toString() = s"MockContext()" 16 | ``` 17 | 18 | First let's import the mocking functions for all our topics 19 | 20 | ```scala mdoc 21 | import eu.monniot.scala3mock.ScalaMocks.* 22 | ``` 23 | 24 | ## Mocking overloaded, curried and polymorphic methods 25 | Overloaded, curried and polymorphic methods can be mocked by specifying either argument types or type parameters. 26 | 27 | ### Overloaded methods 28 | 29 | ```scala mdoc:nest 30 | trait Foo { 31 | def overloaded(x: Int): String 32 | def overloaded(x: String): String 33 | def overloaded[T](x: T): String 34 | } 35 | 36 | val fooMock = mock[Foo] 37 | 38 | when(fooMock.overloaded(_: Int)).expects(10) 39 | when(fooMock.overloaded(_: String)).expects("foo") 40 | when(fooMock.overloaded[Double]).expects(1.23) 41 | ``` 42 | 43 | ### Polymorphic methods 44 | 45 | ```scala mdoc:nest 46 | trait Foo { 47 | def polymorphic[T](x: List[T]): String 48 | } 49 | 50 | val fooMock = mock[Foo] 51 | 52 | when(fooMock.polymorphic(_: List[Int])).expects(List(1, 2, 3)) 53 | ``` 54 | 55 | 56 | ### Curried methods 57 | 58 | ```scala mdoc:nest 59 | trait Foo { 60 | def curried(x: Int)(y: Double): String 61 | } 62 | 63 | val fooMock = mock[Foo] 64 | 65 | when(fooMock.curried(_: Int)(_: Double)).expects(10, 1.23) 66 | ``` 67 | 68 | The project have an action item to make the following statement compile: 69 | 70 | ```scala mdoc:nest:crash 71 | trait Foo { 72 | def curried(x: Int)(y: Double): String 73 | } 74 | 75 | val fooMock = mock[Foo] 76 | 77 | when(fooMock.curried) 78 | ``` 79 | 80 | At the moment the `when` macro isn't smart enough to recognize a curried function whereas the `mock` macro is. That result in the underlying mock correctly creating a `MockFunction2[Int, Double, String]` (two args) but the `when` macro exposes a `MockFunction1[Int, Double => String]` (one arg). 81 | 82 | 83 | ## Methods with implicit parameters 84 | 85 | This case is very similar to curried methods. All you need to do is to help the Scala compiler know that `memcachedMock.get` should be converted to `MockFunction2`. For example: 86 | 87 | ```scala mdoc:nest 88 | class Codec() 89 | 90 | trait Memcached { 91 | def get(key: String)(implicit codec: Codec): Option[Int] 92 | } 93 | 94 | val memcachedMock = mock[Memcached] 95 | 96 | implicit val codec = new Codec 97 | when(memcachedMock.get(_ : String)(_ : Codec)).expects("some_key", *).returning(Some(123)) 98 | ``` 99 | 100 | ### Repeated parameters 101 | 102 | Repeated parameters are represented as a Seq. For example, given: 103 | 104 | 105 | ```scala mdoc:nest 106 | trait Foo { 107 | def takesRepeatedParameter(x: Int, ys: String*): Unit 108 | } 109 | 110 | val fooMock = mock[Foo] 111 | 112 | when(fooMock.takesRepeatedParameter).expects(42, Seq("red", "green", "blue")) 113 | ``` 114 | 115 | ## Returning values (onCall) 116 | 117 | By default mocks and stubs return `null`. You can return predefined value using the `returning` method. When the returned value depends on function arguments, you can return the computed value (or throw a computed exception) with `onCall`. For example: 118 | 119 | 120 | ```scala mdoc:nest 121 | trait Foo { 122 | def increment(a: Int): Int 123 | } 124 | 125 | val fooMock = mock[Foo] 126 | 127 | when(fooMock.increment).expects(12).returning(13) 128 | assert(fooMock.increment(12) == 13) 129 | 130 | when(fooMock.increment).expects(*).onCall { (arg: Int) => arg + 1} 131 | assert(fooMock.increment(100) == 101) 132 | 133 | when(fooMock.increment).expects(*).onCall { arg => throw new RuntimeException("message") } 134 | try { 135 | fooMock.increment(0) 136 | false 137 | } catch { 138 | case _: RuntimeException => true 139 | case _ => false 140 | } 141 | ``` 142 | 143 | ```scala mdoc:nest 144 | val mockIncrement = mockFunction[Int, Int] 145 | mockIncrement.expects(*).onCall { (arg: Int) => arg + 1 } 146 | assert(mockIncrement(10) == 11) 147 | ``` 148 | 149 | ## Call count 150 | 151 | By default, mocks expect exactly one call. Alternative constraints can be set with `repeated`: 152 | 153 | ```scala mdoc:nest 154 | val mockedFunction = mockFunction[Int, Int] 155 | 156 | mockedFunction.expects(42).returns(42).repeated(3 to 7) 157 | mockedFunction.expects(3).repeated(10) 158 | ``` 159 | 160 | There are various aliases for common expectations: 161 | 162 | ```scala mdoc:nest 163 | val mockedFunction1 = mockFunction[Int, String] 164 | val mockedFunction2 = mockFunction[Int, String] 165 | val mockedFunction3 = mockFunction[Int, String] 166 | val mockedFunction4 = mockFunction[Int, String] 167 | val mockedFunction5 = mockFunction[Int, String] 168 | 169 | mockedFunction1.expects(1).returning("foo").once 170 | mockedFunction2.expects(2).returning("foo").noMoreThanTwice 171 | mockedFunction3.expects(3).returning("foo").repeated(3) 172 | mockedFunction4.expects(4).returning("foo").repeated(atLeast = 1, atMost = 2) 173 | mockedFunction5.expects(5).returning("foo").exactly(2) 174 | mockedFunction5.expects(6).returning("foo").never 175 | 176 | mockedFunction1(1) 177 | 178 | mockedFunction3(3) 179 | mockedFunction3(3) 180 | mockedFunction3(3) 181 | 182 | mockedFunction4(4) 183 | mockedFunction4(4) 184 | 185 | mockedFunction5(5) 186 | mockedFunction5(5) 187 | ``` 188 | 189 | For a complete list, see `handlers.CallHandler`. 190 | 191 | ## Exceptions 192 | 193 | Instead of returning a value, mock can be instructed to throw an exception. This can be achieved either by throwing an exception in `onCall` or by using the `throws` method. 194 | 195 | ```scala mdoc:nest 196 | trait Foo { 197 | def increment(a: Int): Int 198 | } 199 | 200 | val fooMock = mock[Foo] 201 | 202 | 203 | // Using the throws or throwing function 204 | when(fooMock.increment).expects(5).throws(new RuntimeException("message")) 205 | when(fooMock.increment).expects(6).throwing(new RuntimeException("message")) 206 | 207 | try { 208 | fooMock.increment(5) 209 | false 210 | } catch { 211 | case _: RuntimeException => true 212 | case _ => false 213 | } 214 | 215 | 216 | // Throwing in an onCall definition 217 | 218 | when(fooMock.increment).expects(*).onCall { (i) => 219 | if(i==0) throw new RuntimeException("i == 0") 220 | else i + 1 221 | } 222 | 223 | try { 224 | fooMock.increment(0) 225 | false 226 | } catch { 227 | case _: RuntimeException => true 228 | case _ => false 229 | } 230 | ``` 231 | 232 | ## Argument Capture 233 | 234 | ScalaMock support capturing argument when using mocks. This allow the usage of `MatchAny` while asserting the argument after the fact. 235 | 236 | Scala3Mock doesn't current support this feature, although nothing in the library actively prevent its inclusion. Contribution welcome. 237 | 238 | > See https://github.com/fmonniot/scala3mock/issues/5 if you'd like to help. 239 | 240 | ## Mocking 0-parameter functions 241 | 242 | Mocking methods which have zero parameters or are parameter-less are a bit different. The `when` macro take a reference to the function without applying it. In the case of parameterless functions, such reference is indiguishable from applying it. Zero-parameters function could theoritically be seen as different, but that could lead to confusion (and indeed, it was legal in Scala 2 and turned out to be confusing). 243 | 244 | To go around this, you can simply wrap the method into a `() => mock.method` function: 245 | 246 | ```scala mdoc 247 | trait Test { 248 | def parameterless: Int 249 | def zeroParameter(): Int 250 | } 251 | 252 | val m = mock[Test] 253 | 254 | when(() => m.zeroParameter()).expects().returns(1) 255 | when(() => m.parameterless).expects().returns(1) 256 | ``` 257 | -------------------------------------------------------------------------------- /docs/user-guide/cats.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Cats Integration 3 | --- 4 | 5 | Scala3Mock comes with a Cats integration that provides a `withExpectations` function that work with a `MonadError[?, Throwable]` instead of the simple value that the default library provide. This is pretty useful if your tests are defined in term of monads (Cats-Effect's `IO`, Scala's `Future` via alleycats, etc…). 6 | 7 | You'll need to add a new dependency to your **build.sbt**: 8 | ```scala 9 | libraryDependencies += "eu.monniot" %% "scala3mock-cats" % "@VERSION@" % Test 10 | ``` 11 | 12 | Once added, simply replace the `ScalaMocks` import with the Cats one: 13 | 14 | ```diff 15 | - import eu.monniot.scala3mock.ScalaMocks.* 16 | + import eu.monniot.scala3mock.cats.ScalaMocks.* 17 | ``` 18 | 19 | You can then use the library like usual, with the value passed to `withExpectations` being a monad instead of any values. 20 | 21 | ```scala mdoc 22 | import eu.monniot.scala3mock.cats.ScalaMocks.* 23 | import cats.effect.SyncIO 24 | 25 | val fa = withExpectations() { 26 | val fn = mockFunction[Int, String] 27 | fn.expects(*).returns("Hello reader") 28 | 29 | SyncIO(fn(2)) 30 | } 31 | 32 | fa.unsafeRunSync() 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/user-guide/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: FAQ 3 | --- 4 | 5 | This section answers some of the most common questions that have been asked over time. It also offers some −at times− helpful debug tips. 6 | 7 | 8 | ## Can I mock final / private methods or classes? 9 | This is not supported, as mocks generated with macros are implemented as subclasses of the type to mock. So private and final methods cannot be overridden. You may want to try using an adapter or façade in your code to make it testable. It is better to test against a trait/interface instead of a concrete implementation. There are libraries that support this kind of mocking, such as [PowerMock](http://powermock.github.io/). Be aware that this kind of mocking involves Bytecode manipulation (to remove the final/private qualifier), which has the risk that your test double diverges from the actual implementation. 10 | 11 | ## Can I mock val / lazy val? 12 | No, the Scala compiler will not allow overriding a `val` with a `def`, so with ScalaMock this is not possible (because mocks are "just" configurable functions). If you can, it is better to design a trait with a def and mock that instead. The concrete implementation can still override that def with a val to give invariant behaviour. Do note that an abstract `val` will be filled out with its `Default` typeclass when going through the `mock` macro. 13 | 14 | ## Can I mock objects? 15 | No, macros cannot generate a subclass of a singleton object, sorry. A way to refactor your code may be to create a trait with the function(s) you want to mock and extend that with the object. Or use Dependency Injection (with classes or functions) to abstract your code and make it testable. 16 | 17 | ## Can I mock hashcode, equals, clone or toString ? 18 | No - all methods from `java.lang.Object` are filtered out during Mock creation. `toString` is particularly problematic as it can cause an infinite loop blowing up the stack on test failure. 19 | 20 | If your interface requires you to implement clone for example, you can create an abstract class that extends your interface with the `Object` methods you need. That should make those methods available to Scala3Mock. 21 | 22 | ## Can I mock static Java calls? 23 | 24 | Scala3Mock works by creating a subclass and instantiating said subclasses. As static methods are defined on the original class itself, Scala3Mock cannot mock them. We recommend you use Dependency Injection, higher-order functions or OO patterns (interfaces, facades, etc) instead of depending directly on implementations. 25 | 26 | ## Using Scala3Mock with Scala 3.3.x 27 | 28 | Unfortunately this serie of the Scala compiler [has a bug](https://github.com/fmonniot/scala3mock/pull/2) that, at the time of writing, is only fixed [in 3.4.x](https://github.com/lampepfl/dotty/pull/18092). 29 | 30 | Fortunately, the workaround for users of this library is pretty simple: when using the `when` macro, provide all the required types. For example, instead of `when(m.oneParam).expects(List(2)).returns("hello")` you'd have to write `when[List[Int], String](m.oneParam).expects(List(2)).returns("hello")`. 31 | 32 | ## Moving from ScalaMock to Scala3Mock 33 | 34 | While most features have been ported from ScalaMock, there is still some changes to consider: 35 | 36 | - Some of the existing syntax has change quite a bit 37 | - Stub feature dropped 38 | - No cross compilation with Scala2 39 | - Support for ScalaTest's `path.FunSpec` removed (help welcome) 40 | - Support for inner types ([help welcome](https://github.com/fmonniot/scala3mock/issues/3)) 41 | - By-name parameters ([help welcome](https://github.com/fmonniot/scala3mock/issues/4)) currently have issues with type inference, but work fine when fully specifiying types when defining the expectations (eg. `when[String, Int](mock.function).expects("val").returns(1)`) 42 | -------------------------------------------------------------------------------- /docs/user-guide/features.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Features 3 | --- 4 | 5 | This is an overview of Scala3Mock features. Some section in this page have a dedicated page as well. Those pages will be indicated when present. 6 | 7 | Do note that although the authors tried to be thorough when writing this page, some features might have slipped through the cracks. All supported use cases are covered by a test in the [core/features](https://github.com/fmonniot/scala3mock/tree/main/core/src/test/scala/eu/monniot/scala3mock/features) and [core/mock](https://github.com/fmonniot/scala3mock/tree/main/core/src/test/scala/eu/monniot/scala3mock/mock) packages. 8 | 9 | 10 | Scala3Mock provides fully type-safe support for almost all Scala features. 11 | 12 | This includes: 13 | 14 | - mocking classes, traits and case classes 15 | - mocking functions and operators 16 | - mocking type parametrised and overloaded methods 17 | - support for type constraints 18 | - support for repeated parameters and named parameters 19 | - mocking Java classes and interfaces 20 | 21 | Before going over the various features, let's define some types and import we can use along the way. 22 | 23 | ```scala mdoc 24 | trait Heater { 25 | def isReady: Boolean 26 | } 27 | 28 | class CoffeeMachine(heater: Heater) { 29 | def powerOn(): Unit = () 30 | def powerOff(): Unit = () 31 | 32 | def isOn: Boolean = ??? 33 | } 34 | 35 | case class Player(name: String, country: String) 36 | case class Message(content: String) 37 | 38 | abstract class Database { 39 | def getPlayerByName(name: String): Player 40 | def sendMessage(player: Player, message: Message): Unit 41 | 42 | def someMethod(s: String, d: Int): Int 43 | def otherMethod(s: String, d: Float): Int 44 | 45 | def increment(i: Int): Int 46 | } 47 | 48 | // And import the scala3mock content. 49 | import eu.monniot.scala3mock.ScalaMocks.* 50 | ``` 51 | 52 | ```scala mdoc:invisible 53 | // Make shift mock context so that we can call `mock` without constraint. 54 | // Don't do that in your test cases folks :) 55 | given eu.monniot.scala3mock.context.MockContext = 56 | new eu.monniot.scala3mock.context.MockContext: 57 | override type ExpectationException = eu.monniot.scala3mock.MockExpectationFailed 58 | 59 | override def newExpectationException(message: String, methodName: Option[String]): ExpectationException = 60 | eu.monniot.scala3mock.MockExpectationFailed(message, methodName) 61 | 62 | override def toString() = s"MockContext()" 63 | ``` 64 | 65 | ## Mocking 66 | 67 | Scala3Mock can create mock out of classes (abstract or not, with parameters or not) and traits. 68 | 69 | ```scala mdoc 70 | val heaterMock = mock[Heater] 71 | val dbMock = mock[Database] 72 | ``` 73 | 74 | ## Argument matching 75 | 76 | ```scala mdoc:invisible 77 | // This is a hack because for some reason mdoc doesn't work very well 78 | // with the tilde extension method. We have a test in the lib to check 79 | // that there is no need for this re-definition in user's code. 80 | extension (value: Double) 81 | def unary_~ = eu.monniot.scala3mock.matchers.MatchEpsilon(value) 82 | ``` 83 | 84 | ```scala mdoc 85 | // expect someMethod("foo", 42) to be called 86 | when(dbMock.someMethod).expects("foo", 42) 87 | 88 | // expect someMethod("foo", x) to be called for some integer x 89 | when(dbMock.someMethod).expects("foo", *) 90 | 91 | // expect someMethod("foo", x) to be called for some float x that is close to 42.0 92 | when(dbMock.otherMethod).expects("foo", ~42.0) 93 | 94 | // expect sendMessage(receiver, message) for some receiver with name starting with "A" 95 | when(dbMock.sendMessage).expects(where { (receiver: Player, message: Message) => 96 | receiver.name.startsWith("A") 97 | }) 98 | ``` 99 | 100 | ## Call Ordering 101 | 102 | Mock ordering is not currently supported in Scala3Mock. If you wish it to be, please chime in on [issue #6](https://github.com/fmonniot/scala3mock/issues/6). 103 | 104 | ```scala mdoc:fail:invisible 105 | // Keeping the snippet here just in case I end up implementing ordering 106 | 107 | // expect that machine is turned on before turning it off 108 | inSequence { 109 | (machineMock.turnOn _).expects() 110 | (machineMock.turnOff _).expects() 111 | } 112 | 113 | // players can be fetched in any order 114 | inAnyOrder { 115 | (databaseMock.getPlayerByName _).expects("Hans") 116 | (databaseMock.getPlayerByName _).expects("Boris") 117 | } 118 | ``` 119 | 120 | ## Call count 121 | 122 | ```scala mdoc:silent 123 | // expect message to be sent twice 124 | when(dbMock.sendMessage).expects(*, *).twice 125 | 126 | // expect message to be sent any number of times 127 | when(dbMock.sendMessage).expects(*, *).anyNumberOfTimes 128 | 129 | // expect message to be sent no more than twice 130 | when(dbMock.sendMessage).expects(*, *).noMoreThanTwice 131 | ``` 132 | 133 | ## Returning values 134 | 135 | ```scala mdoc:silent 136 | when(dbMock.getPlayerByName).expects("Hans").returning(Player(name="Hans", country="Germany")) 137 | when(dbMock.getPlayerByName).expects("Boris").returning(Player(name="Hans", country="Russia")) 138 | ``` 139 | 140 | ## Throwing exceptions 141 | 142 | ```scala mdoc:silent 143 | when(dbMock.getPlayerByName).expects("George").throwing(new NoSuchElementException) 144 | ``` 145 | 146 | ## Call Handlers 147 | 148 | If your mock needs to do more complex calculation, `onCall` will let you do exactly that: 149 | 150 | ```scala mdoc:silent 151 | when(dbMock.increment).expects(*).onCall { 152 | case 0 => throw RuntimeException("0 will throw") 153 | case arg => arg + 1 154 | }.twice 155 | 156 | // compute returned value 157 | assert(dbMock.increment(100) == 101) 158 | 159 | // throw computed exception 160 | try dbMock.increment(0) 161 | catch e => println(s"caught $e") 162 | ``` 163 | -------------------------------------------------------------------------------- /docs/user-guide/matching.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Argument matching 3 | --- 4 | 5 | Scala3Mock support three types of matching mocked functions arguments: 6 | 7 | - any matching, also called wildcards 8 | - epsilon matching, 9 | - predicate matching 10 | 11 | 12 | In this page we will make use of a few common declarations, so let's get them out of the way 13 | 14 | ```scala mdoc:invisible 15 | // Make shift mock context so that we can call `mock` without constraint. 16 | // Don't do that in your test cases folks :) 17 | given eu.monniot.scala3mock.context.MockContext = 18 | new eu.monniot.scala3mock.context.MockContext: 19 | override type ExpectationException = eu.monniot.scala3mock.MockExpectationFailed 20 | 21 | override def newExpectationException(message: String, methodName: Option[String]): ExpectationException = 22 | eu.monniot.scala3mock.MockExpectationFailed(message, methodName) 23 | 24 | override def toString() = s"MockContext()" 25 | ``` 26 | 27 | ```scala mdoc 28 | import eu.monniot.scala3mock.ScalaMocks.* 29 | 30 | val mockedFunction = mockFunction[String, Any, Unit] 31 | ``` 32 | 33 | ## Any matching 34 | 35 | Any matching are defined using the `MatchAny` class, or more commonly its `*` alias. For example: 36 | 37 | ```scala mdoc 38 | mockedFunction.expects("", *).anyNumberOfTimes 39 | ``` 40 | 41 | will match any of the following: 42 | 43 | ```scala mdoc 44 | mockedFunction("", "") 45 | mockedFunction("", 1.0) 46 | mockedFunction("", null) 47 | mockedFunction("", Object()) 48 | ``` 49 | 50 | ## Predicate matching 51 | 52 | More complicated argument matching can be implemented by using `where` to provide your own logic. 53 | 54 | ```scala 55 | trait MatchPredicate: 56 | def where[Arg1](predicate: (Arg1) => Boolean) 57 | def where[Arg1, Arg2](predicate: (Arg1, Arg2) => Boolean) 58 | ``` 59 | 60 | ### Example 1 61 | 62 | In this example we will use the following `PlayerLeaderboard` interface. 63 | 64 | ```scala mdoc 65 | case class Player(id: Long, name: String, emailAddress: String, country: String) 66 | 67 | trait PlayerLeaderBoard { 68 | def addPointsForPlayer(player: Player, points: Int): Unit 69 | } 70 | 71 | val leaderBoardMock = mock[PlayerLeaderBoard] 72 | ``` 73 | 74 | Now imagine that we want to put an expectation that `addPointsForPlayer` is called with: 75 | 76 | - `points` equal to 100, and 77 | - `player` can have any `name`, any `email`, any `country`, as long as its `id` is 789. 78 | 79 | Achieving that can be done using the `where` predicate: 80 | 81 | ```scala mdoc 82 | when(leaderBoardMock.addPointsForPlayer).expects(where { 83 | (player: Player, points: Int) => player.id == 789 && points == 100 84 | }) 85 | ``` 86 | 87 | ### Example 2 88 | 89 | This second example is simpler but shows the power of using arbitrary predicate. Here the mock will only return if the first argument is smaller than the second one. 90 | 91 | ```scala mdoc 92 | val mockedFunction2 = mockFunction[Double, Double, Unit] // (Double, Double) => Unit 93 | mockedFunction2.expects(where { _ < _ }) // expects that arg1 < arg2 94 | ``` 95 | 96 | 97 | ## Epsilon matching 98 | 99 | Epsilon matching is useful when dealing with floating point values. An epsilon match is specified with the `MatchEpsilon` class, or more commonly its `~` alias: 100 | 101 | ```scala mdoc 102 | val mockedFunction3 = mockFunction[Double, Unit] // (Double, Double) => Unit 103 | 104 | mockedFunction3.expects(~42.0).anyNumberOfTimes 105 | ``` 106 | 107 | will match: 108 | 109 | ```scala mdoc 110 | mockedFunction3(42.0) 111 | mockedFunction3(42.0001) 112 | mockedFunction3(41.9999) 113 | ``` 114 | 115 | but will not match: 116 | 117 | ```scala mdoc:crash 118 | mockedFunction3(43.0) 119 | mockedFunction3(42.1) 120 | ``` 121 | -------------------------------------------------------------------------------- /docs/user-guide/scalatest.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ScalaTest Integration 3 | --- 4 | 5 | ```scala mdoc:invisible 6 | // This code block provide the classes required to compile the other snippet. 7 | import scala.concurrent.Future 8 | 9 | class WaterContainer { 10 | def isEmpty: Boolean = ??? 11 | def isOverfull: Boolean = ??? 12 | } 13 | 14 | case class Currency(id: String, valueToUSD: Double, change: Double) 15 | class CurrencyDatabase() { 16 | def getCurrency(id: String): Currency = ??? 17 | } 18 | class ExchangeRateListing(db: CurrencyDatabase) { 19 | def getExchangeRate(a: String, b: String): Future[Double] = ??? 20 | } 21 | 22 | class Heater() { 23 | def isReady: Boolean = ??? 24 | } 25 | class CoffeeMachine(a: WaterContainer, b: Heater) { 26 | def powerOn(): Unit = () 27 | def powerOff(): Unit = () 28 | 29 | def isOn: Boolean = ??? 30 | } 31 | 32 | ``` 33 | 34 | ## Integration 35 | 36 | To provide mocking support in ScalaTest, extends your suite with `eu.monniot.scala3mock.scalatest.MockFactory`: 37 | 38 | 39 | ```scala mdoc 40 | import org.scalatest.flatspec.AnyFlatSpec 41 | import eu.monniot.scala3mock.scalatest.MockFactory 42 | 43 | class CoffeeMachineTest extends AnyFlatSpec with MockFactory { 44 | 45 | "CoffeeMachine" should "not turn on the heater when the water container is empty" in { 46 | val waterContainerMock = mock[WaterContainer] 47 | when(() => waterContainerMock.isEmpty).expects().returning(true) 48 | // ... 49 | } 50 | } 51 | ``` 52 | 53 | When using the asynchronous version, please use `eu.monniot.scala3mock.scalatest.AsyncMockFactory` instead: 54 | 55 | ```scala mdoc 56 | import org.scalatest.flatspec.AsyncFlatSpec 57 | import eu.monniot.scala3mock.scalatest.AsyncMockFactory 58 | 59 | class ExchangeRateListingTest extends AsyncFlatSpec with AsyncMockFactory { 60 | 61 | val eur = Currency(id = "EUR", valueToUSD = 1.0531, change = -0.0016) 62 | val gpb = Currency(id = "GPB", valueToUSD = 1.2280, change = -0.0012) 63 | val aud = Currency(id = "AUD", valueToUSD = 0.7656, change = -0.0024) 64 | 65 | "ExchangeRateListing" should "eventually return the exchange rate between passed Currencies when getExchangeRate is invoked" in { 66 | val currencyDatabaseMock = mock[CurrencyDatabase] 67 | 68 | when(currencyDatabaseMock.getCurrency).expects(eur.id).returns(eur) 69 | when(currencyDatabaseMock.getCurrency).expects(gpb.id).returns(gpb) 70 | when(currencyDatabaseMock.getCurrency).expects(aud.id).returns(aud) 71 | 72 | val listing = ExchangeRateListing(currencyDatabaseMock) 73 | 74 | val future: Future[Double] = listing.getExchangeRate(eur.id, gpb.id) 75 | 76 | future.map { exchangeRate => 77 | assert(exchangeRate == eur.valueToUSD / gpb.valueToUSD) 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | ## Sharing mocks and expectations in ScalaTest 84 | 85 | Sometimes multiple test cases will require the same mocks (and more generally the same fixtures like databases, sockets). There are many ways to achieve that goal, Scala3Mock officialy support only two: 86 | 87 | - _isolated test cases_: clean and simple, recommended when all tests have the same or very similar mocks 88 | - _fixture contexts_: more flexible, recommended for complex test suites when a single set of mocks does not fit all test cases 89 | 90 | ### Isolated test cases 91 | 92 | If you mix the `org.scalatest.OneInstancePerTest` trait to your test suite, each test case will be run in its own instance of the class suite. That means each test will get a different copy of the instance variables. 93 | 94 | In the suite scope, you can 95 | - declare instance variables (eg. mocks) that will be used by multiple test cases, and 96 | - perform common test cases setup (eg. set up various mock expectations). 97 | 98 | Because each test case has its own instance, different test cases do not interfere with each other. 99 | 100 | ```scala mdoc 101 | import org.scalatest.OneInstancePerTest 102 | import org.scalatest.matchers.should.Matchers 103 | 104 | // Please note that this test suite mixes in OneInstancePerTest 105 | class CoffeeMachineTest2 extends AnyFlatSpec with Matchers with OneInstancePerTest with MockFactory { 106 | // shared objects 107 | val waterContainerMock = mock[WaterContainer] 108 | val heaterMock = mock[Heater] 109 | val coffeeMachine = CoffeeMachine(waterContainerMock, heaterMock) 110 | 111 | // you can set common expectations in the suite scope 112 | when(() => heaterMock.isReady).expects().returning(true) 113 | 114 | // and perform common test setup 115 | coffeeMachine.powerOn() 116 | 117 | "CoffeeMachine" should "not turn on the heater when the water container is empty" in { 118 | coffeeMachine.isOn shouldBe true 119 | when(() => waterContainerMock.isEmpty).expects().returning(true) 120 | 121 | // ... 122 | coffeeMachine.powerOff() 123 | coffeeMachine.isOn shouldBe false 124 | } 125 | 126 | it should "not turn on the heater when the water container is overfull" in { 127 | // each test case uses a separate, fresh test suite so the coffee machine is turned on 128 | // even if previous test case turned it off 129 | coffeeMachine.isOn shouldBe true 130 | // ... 131 | } 132 | } 133 | ``` 134 | 135 | Note that if you need to create costly fixtures (eg. establish a database connection), this strategy will duplicate that costly work by the amount of tests. In those cases, we instead recommend to use fixture contexts. 136 | 137 | ### Fixture contexts 138 | 139 | Another way of sharing complex context between tests is to have separate fixture context. In practice that means creating a new trait/class that is instantiated per test. Because each test has its own instance, the context isn't shared between tests and because there is only one class suite you can use it to store common and expensive fixtures. 140 | 141 | It's really easy to apply to regular test suites: 142 | 143 | ```scala mdoc 144 | 145 | class CoffeeMachineTest3 extends AnyFlatSpec with Matchers with MockFactory { 146 | trait Test { // fixture context 147 | // shared objects 148 | val waterContainerMock = mock[WaterContainer] 149 | val heaterMock = mock[Heater] 150 | val coffeeMachine = CoffeeMachine(waterContainerMock, heaterMock) 151 | 152 | // test setup 153 | coffeeMachine.powerOn() 154 | } 155 | 156 | "CoffeeMachine" should "not turn on the heater when the water container is empty" in new Test { 157 | coffeeMachine.isOn shouldBe true 158 | when(() => waterContainerMock.isEmpty).expects().returning(true) 159 | // ... 160 | } 161 | 162 | // you can extend and combine fixture-contexts 163 | trait OverfullWaterContainerTest extends Test { 164 | // you can set expectations and use mocks in the fixture-context 165 | when(() => waterContainerMock.isOverfull).expects().returning(true) 166 | 167 | // and define helper functions 168 | def sharedComplexLogic(): Unit = { 169 | coffeeMachine.powerOff() 170 | // ... 171 | } 172 | } 173 | 174 | it should "not turn on the heater when the water container is overfull" in new OverfullWaterContainerTest { 175 | // ... 176 | sharedComplexLogic() 177 | } 178 | } 179 | ``` 180 | 181 | This pattern can also be used with async test suite, but because those needs to return a `Future` (or equivalent) we cannot just return the fixture context directly. 182 | 183 | 184 | ```scala mdoc 185 | class ExchangeRateListingTest2 extends AsyncFlatSpec with AsyncMockFactory { 186 | 187 | class Context { 188 | val eur = Currency(id = "EUR", valueToUSD = 1.0531, change = -0.0016) 189 | val gpb = Currency(id = "GPB", valueToUSD = 1.2280, change = -0.0012) 190 | val aud = Currency(id = "AUD", valueToUSD = 0.7656, change = -0.0024) 191 | 192 | val currencyDatabaseMock = mock[CurrencyDatabase] 193 | 194 | when(currencyDatabaseMock.getCurrency).expects(eur.id).returns(eur) 195 | when(currencyDatabaseMock.getCurrency).expects(gpb.id).returns(gpb) 196 | when(currencyDatabaseMock.getCurrency).expects(aud.id).returns(aud) 197 | 198 | val listing = ExchangeRateListing(currencyDatabaseMock) 199 | } 200 | 201 | 202 | "ExchangeRateListing" should "eventually return the exchange rate between passed Currencies when getExchangeRate is invoked" in { 203 | val ctx = Context() 204 | 205 | val future: Future[Double] = ctx.listing.getExchangeRate(ctx.eur.id, ctx.gpb.id) 206 | 207 | future.map { exchangeRate => 208 | assert(exchangeRate == ctx.eur.valueToUSD / ctx.gpb.valueToUSD) 209 | } 210 | } 211 | } 212 | ``` 213 | 214 | 215 | ## Advanced 216 | 217 | > TODO: how it works under the hood. Give context to debug some weird errors if the `MockContext` is 218 | > either not available or corrupted. 219 | -------------------------------------------------------------------------------- /integration/src/test: -------------------------------------------------------------------------------- 1 | ../../core/src/test -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.10.7 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.8") 2 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") 3 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.9.2") 4 | -------------------------------------------------------------------------------- /scalatest/src/main/scala/eu/monniot/scala3mock/scalatest/AsyncMockFactory.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.scalatest 2 | 3 | import eu.monniot.scala3mock.context.{Mock, MockContext} 4 | import eu.monniot.scala3mock.macros.{MockImpl, WhenImpl} 5 | import eu.monniot.scala3mock.functions.* 6 | import eu.monniot.scala3mock.MockExpectationFailed 7 | import eu.monniot.scala3mock.handlers.UnorderedHandlers 8 | import scala.collection.mutable.ListBuffer 9 | import eu.monniot.scala3mock.context.Call 10 | 11 | import org.scalatest.{AsyncTestSuite, AsyncTestSuiteMixin, FutureOutcome} 12 | import scala.concurrent.Future 13 | import scala.util.Success 14 | import scala.util.Failure 15 | import scala.util.control.NonFatal 16 | import org.scalatest.Exceptional 17 | 18 | trait AsyncMockFactory extends AsyncTestSuiteMixin with BaseFactory: 19 | this: AsyncTestSuite => // To prevent users from using Sync with Async suites 20 | 21 | private def withExpectations[T](test: => Future[T]): Future[T] = 22 | initializeExpectations() 23 | val testResult = test.map { result => 24 | verifyExpectations() 25 | result 26 | } 27 | 28 | testResult onComplete { 29 | case Success(_) => () 30 | case Failure(_) => clearTestExpectations() 31 | } 32 | 33 | testResult 34 | 35 | abstract override def withFixture(test: NoArgAsyncTest): FutureOutcome = 36 | if (autoVerify) 37 | new FutureOutcome( 38 | withExpectations(super.withFixture(test).toFuture).recoverWith({ 39 | case NonFatal(ex) => Future.successful(Exceptional(ex)) 40 | }) 41 | ) 42 | else 43 | super.withFixture(test) 44 | -------------------------------------------------------------------------------- /scalatest/src/main/scala/eu/monniot/scala3mock/scalatest/BaseFactory.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.scalatest 2 | 3 | import eu.monniot.scala3mock.context.{Mock, MockContext} 4 | import eu.monniot.scala3mock.macros.Mocks 5 | import eu.monniot.scala3mock.functions.MockFunctions 6 | import eu.monniot.scala3mock.MockExpectationFailed 7 | import eu.monniot.scala3mock.handlers.UnorderedHandlers 8 | import eu.monniot.scala3mock.matchers.Matchers 9 | import scala.collection.mutable.ListBuffer 10 | import eu.monniot.scala3mock.context.Call 11 | import eu.monniot.scala3mock.macros.Mocks 12 | import scala.util.control.NonFatal 13 | import org.scalatest.TestSuite 14 | import org.scalatest.TestSuiteMixin 15 | import org.scalatest.Outcome 16 | import org.scalatest.Failed 17 | import org.scalatest.exceptions.TestFailedException 18 | import org.scalatest.exceptions.StackDepthException 19 | import eu.monniot.scala3mock.handlers.Handlers 20 | 21 | trait BaseFactory extends MockFunctions with Mocks with Matchers: 22 | 23 | protected var autoVerify = true 24 | 25 | private val suiteCallLog: ListBuffer[Call] = new ListBuffer[Call] 26 | private val suiteExpectationContext: Handlers = new UnorderedHandlers 27 | 28 | // We should have two expectationContext & callLog: 29 | // - One at the suite level 30 | // - A second at the test level 31 | // The second one should inherit from the first as well 32 | given currentContext: MockContext = new MockContext: 33 | override type ExpectationException = TestFailedException 34 | 35 | override def newExpectationException( 36 | message: String, 37 | methodName: Option[String] 38 | ): ExpectationException = 39 | new TestFailedException( 40 | (_: StackDepthException) => Some(message), 41 | None, 42 | failedCodeStackDepthFn(methodName) 43 | ) 44 | 45 | override def toString() = s"MockContext(callLog = $callLog)" 46 | 47 | // Initialize the current context with the suite-level calls and expectations 48 | callLog = suiteCallLog 49 | expectationContext = suiteExpectationContext 50 | 51 | private def failedCodeStackDepthFn( 52 | methodName: Option[String] 53 | ): StackDepthException => Int = e => 54 | e.getStackTrace indexWhere { s => 55 | !s.getClassName.startsWith("org.scalamock") && !s.getClassName.startsWith( 56 | "org.scalatest" 57 | ) && 58 | !(s.getMethodName == "newExpectationException") && !(s.getMethodName == "reportUnexpectedCall") && 59 | methodName.forall(s.getMethodName != _) 60 | } 61 | 62 | private[scalatest] def initializeExpectations(): Unit = 63 | currentContext.callLog = new ListBuffer[Call] 64 | currentContext.expectationContext = new UnorderedHandlers 65 | 66 | // Import the suite calls & expectations into the test one 67 | currentContext.callLog.addAll(suiteCallLog) 68 | suiteExpectationContext.list.foreach(currentContext.expectationContext.add) 69 | 70 | private[scalatest] def verifyExpectations(): Unit = 71 | currentContext.callLog.foreach(currentContext.expectationContext.verify) 72 | 73 | // We need to call this before clearing test expectations. Otherwise the call 74 | // and expectations made during suite initialization will be cleared before 75 | // checking if they have been used. Note that it's an issue because Handler 76 | // are mutable and suite/current expectation context share the same references. 77 | val isSatisfied = currentContext.expectationContext.isSatisfied 78 | 79 | val oldCallLog = currentContext.callLog 80 | val oldExpectationContext = currentContext.expectationContext 81 | 82 | clearTestExpectations() 83 | 84 | if !isSatisfied then 85 | currentContext.reportUnsatisfiedExpectation( 86 | oldCallLog, 87 | oldExpectationContext 88 | ) 89 | 90 | private[scalatest] def clearTestExpectations(): Unit = 91 | // to forbid setting expectations after verification is done 92 | currentContext.callLog = suiteCallLog 93 | currentContext.expectationContext = suiteExpectationContext 94 | suiteExpectationContext.list.foreach(_.reset()) 95 | -------------------------------------------------------------------------------- /scalatest/src/main/scala/eu/monniot/scala3mock/scalatest/MockFactory.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.scalatest 2 | 3 | import eu.monniot.scala3mock.context.{Mock, MockContext} 4 | import eu.monniot.scala3mock.macros.{MockImpl, WhenImpl} 5 | import eu.monniot.scala3mock.functions.* 6 | import eu.monniot.scala3mock.MockExpectationFailed 7 | import eu.monniot.scala3mock.handlers.UnorderedHandlers 8 | import scala.collection.mutable.ListBuffer 9 | import eu.monniot.scala3mock.context.Call 10 | import eu.monniot.scala3mock.macros.Mocks 11 | import scala.util.control.NonFatal 12 | import org.scalatest.TestSuite 13 | import org.scalatest.TestSuiteMixin 14 | import org.scalatest.Outcome 15 | import org.scalatest.Failed 16 | import org.scalatest.exceptions.TestFailedException 17 | import org.scalatest.exceptions.StackDepthException 18 | import eu.monniot.scala3mock.handlers.Handlers 19 | 20 | trait MockFactory extends TestSuiteMixin with BaseFactory: 21 | this: TestSuite => // To prevent users from using Async with non-Async suites 22 | 23 | private def withExpectations[T](name: String)(what: => T): T = 24 | try 25 | initializeExpectations() 26 | val result = what 27 | verifyExpectations() 28 | result 29 | catch 30 | case NonFatal(ex) => 31 | clearTestExpectations() 32 | throw ex 33 | 34 | abstract override def withFixture(test: NoArgTest): Outcome = 35 | if (autoVerify) 36 | withExpectations(test.name) { 37 | super.withFixture(test) match 38 | case Failed(throwable) => 39 | // Throw error that caused test failure to prevent hiding it by 40 | // "unsatisfied expectation" exception (see issue #72) 41 | throw throwable 42 | case outcome => outcome 43 | } 44 | else 45 | super.withFixture(test) 46 | -------------------------------------------------------------------------------- /scalatest/src/test/scala/eu/monniot/scala3mock/mockable/TestTrait.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.mockable 2 | 3 | trait TestTrait { 4 | def noParamMethod(): String 5 | def oneParamMethod(param: Int): String 6 | def polymorphicMethod[T](x: List[T]): String 7 | } 8 | -------------------------------------------------------------------------------- /scalatest/src/test/scala/eu/monniot/scala3mock/scalatest/BasicAsyncTests.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.scalatest 2 | 3 | import eu.monniot.scala3mock.scalatest.AsyncMockFactory 4 | import eu.monniot.scala3mock.mockable.TestTrait 5 | import org.scalatest.flatspec.AsyncFlatSpec 6 | import org.scalatest.matchers.should.Matchers 7 | import scala.concurrent.Future 8 | 9 | /** Tests for mock defined in test case scope 10 | */ 11 | class BasicAsyncTest extends AsyncFlatSpec with Matchers with AsyncMockFactory { 12 | 13 | it should "work in scalatest async test" in { 14 | val mockedTrait = mock[TestTrait] 15 | when(() => mockedTrait.noParamMethod()).expects().returning("42") 16 | Future.successful { 17 | mockedTrait.noParamMethod() shouldBe "42" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /scalatest/src/test/scala/eu/monniot/scala3mock/scalatest/BasicTests.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.scalatest 2 | 3 | import eu.monniot.scala3mock.scalatest.MockFactory 4 | import eu.monniot.scala3mock.mockable.TestTrait 5 | import org.scalatest.flatspec.AnyFlatSpec 6 | import org.scalatest.matchers.should.Matchers 7 | 8 | /** Tests for mock defined in test case scope 9 | * 10 | * Tests for issue #25 11 | */ 12 | class BasicTest extends AnyFlatSpec with Matchers with MockFactory { 13 | 14 | it should "allow to use mock defined in test case scope" in { 15 | val mockedTrait = mock[TestTrait] 16 | when(mockedTrait.oneParamMethod).expects(*).returning("one") 17 | when(mockedTrait.oneParamMethod).expects(2).returning("two") 18 | when(() => mockedTrait.noParamMethod()).expects().returning("yey") 19 | 20 | mockedTrait.oneParamMethod(1) shouldBe "one" 21 | mockedTrait.oneParamMethod(2) shouldBe "two" 22 | mockedTrait.noParamMethod() shouldBe "yey" 23 | } 24 | 25 | it should "use separate call logs for each test case" in { 26 | val mockedTrait = mock[TestTrait] 27 | when(mockedTrait.oneParamMethod).expects(3).returning("three") 28 | 29 | mockedTrait.oneParamMethod(3) shouldBe "three" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /scalatest/src/test/scala/eu/monniot/scala3mock/scalatest/ErrorReportingTest.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.scalatest 2 | 3 | import eu.monniot.scala3mock.scalatest.MockFactory 4 | import eu.monniot.scala3mock.mockable.TestTrait 5 | import org.scalatest._ 6 | import org.scalatest.exceptions.TestFailedException 7 | 8 | import scala.language.postfixOps 9 | import org.scalatest.flatspec.AnyFlatSpec 10 | import org.scalatest.funsuite.AnyFunSuite 11 | import org.scalatest.matchers.should.Matchers 12 | 13 | /** Tests that errors are reported correctly in ScalaTest suites 14 | */ 15 | class ErrorReportingTest 16 | extends AnyFlatSpec 17 | with Matchers 18 | with TestSuiteRunner { 19 | 20 | it should "report unexpected call correctly" in { 21 | class TestedSuite extends AnyFunSuite with MockFactory { 22 | test("execute block of code") { 23 | val mockedTrait = mock[TestTrait] 24 | mockedTrait.oneParamMethod(3) 25 | } 26 | } 27 | 28 | val outcome = runTestCase[TestedSuite](new TestedSuite) 29 | val errorMessage = getErrorMessage[TestFailedException](outcome) 30 | errorMessage should startWith("Unexpected call") 31 | } 32 | 33 | it should "report unexpected call correctly when expectations are set" in { 34 | class TestedSuite extends AnyFunSuite with MockFactory { 35 | test("execute block of code") { 36 | val mockedTrait = mock[TestTrait] 37 | when(mockedTrait.oneParamMethod).expects(1).returning("one") 38 | mockedTrait.oneParamMethod(3) 39 | } 40 | } 41 | 42 | // Unexpected call should be reported by ScalaTest 43 | val outcome = runTestCase[TestedSuite](new TestedSuite) 44 | val errorMessage = getErrorMessage[TestFailedException](outcome) 45 | errorMessage should startWith("Unexpected call") 46 | } 47 | 48 | it should "not hide NullPointerException" in { 49 | class TestedSuite extends AnyFunSuite with MockFactory { 50 | test("execute block of code") { 51 | val mockedTrait = mock[TestTrait] 52 | when(mockedTrait.oneParamMethod).expects(1).returning("one") 53 | throw new NullPointerException; 54 | } 55 | } 56 | 57 | val outcome = runTestCase[TestedSuite](new TestedSuite) 58 | // NullPointerException should be reported by ScalaTest 59 | getThrowable[NullPointerException](outcome) shouldBe a[NullPointerException] 60 | } 61 | 62 | it should "report default mock names" in { 63 | class TestedSuite extends AnyFunSuite with MockFactory { 64 | test("execute block of code") { 65 | val mockA = mock[TestTrait] 66 | val mockB = mock[TestTrait] 67 | 68 | when(mockA.oneParamMethod).expects(3) 69 | mockB.oneParamMethod(3) 70 | } 71 | } 72 | 73 | val outcome = runTestCase[TestedSuite](new TestedSuite) 74 | val errorMessage = getErrorMessage[TestFailedException](outcome) 75 | errorMessage shouldBe 76 | """|Unexpected call: TestTrait.oneParamMethod(3) 77 | | 78 | |Expected: 79 | |inAnyOrder { 80 | | TestTrait.oneParamMethod(3) once (never called - UNSATISFIED) 81 | |} 82 | | 83 | |Actual: 84 | | TestTrait.oneParamMethod(3) 85 | """.stripMargin.trim 86 | } 87 | 88 | it should "report unexpected calls in readable manner" in { 89 | // To have a different name being reported 90 | trait OuterTestTrait extends TestTrait 91 | 92 | class TestedSuite extends AnyFunSuite with MockFactory { 93 | val suiteScopeMock = mock[OuterTestTrait] 94 | when(() => suiteScopeMock.noParamMethod()) 95 | .expects() 96 | .returning("two") 97 | .twice 98 | 99 | test("execute block of code") { 100 | val mockedTrait = mock[TestTrait] 101 | when(mockedTrait.polymorphicMethod).expects(List(1)).returning("one") 102 | 103 | suiteScopeMock.noParamMethod() 104 | mockedTrait.oneParamMethod(3) 105 | } 106 | } 107 | 108 | val outcome = runTestCase[TestedSuite](new TestedSuite) 109 | val errorMessage = getErrorMessage[TestFailedException](outcome) 110 | errorMessage shouldBe 111 | """|Unexpected call: TestTrait.oneParamMethod(3) 112 | | 113 | |Expected: 114 | |inAnyOrder { 115 | | OuterTestTrait.noParamMethod() twice (called once - UNSATISFIED) 116 | | TestTrait.polymorphicMethod[T](List(1)) once (never called - UNSATISFIED) 117 | |} 118 | | 119 | |Actual: 120 | | OuterTestTrait.noParamMethod() 121 | | TestTrait.oneParamMethod(3) 122 | """.stripMargin.trim 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /scalatest/src/test/scala/eu/monniot/scala3mock/scalatest/FixtureContextTest.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.scalatest 2 | 3 | import eu.monniot.scala3mock.scalatest.MockFactory 4 | import eu.monniot.scala3mock.mockable.TestTrait 5 | 6 | import org.scalatest.flatspec.AnyFlatSpec 7 | import org.scalatest.matchers.should.Matchers 8 | 9 | /** Tests for mocks defined in fixture-contexts 10 | * 11 | * Tests for issue #25 12 | */ 13 | class FixtureContextTest extends AnyFlatSpec with Matchers with MockFactory { 14 | 15 | trait TestSetup { 16 | val mockedTrait = mock[TestTrait] 17 | val input = 1 18 | val output = "one" 19 | } 20 | 21 | trait TestSetupWithExpectationsPredefined extends TestSetup { 22 | when(mockedTrait.oneParamMethod).expects(input).returning(output) 23 | } 24 | 25 | trait TestSetupWithHandlerCalledDuringInitialization 26 | extends TestSetupWithExpectationsPredefined { 27 | mockedTrait.oneParamMethod(input) shouldBe output 28 | } 29 | 30 | it should "allow to use mock defined in fixture-context" in new TestSetup { 31 | when(mockedTrait.oneParamMethod).expects(input).returning(output) 32 | when(mockedTrait.oneParamMethod).expects(2).returning("two") 33 | 34 | mockedTrait.oneParamMethod(input) shouldBe output 35 | mockedTrait.oneParamMethod(2) shouldBe "two" 36 | } 37 | 38 | it should "allow to use mock defined in fixture-context with expectations predefined" in new TestSetupWithExpectationsPredefined { 39 | when(mockedTrait.oneParamMethod).expects(2).returning("two") 40 | 41 | mockedTrait.oneParamMethod(input) shouldBe output 42 | mockedTrait.oneParamMethod(2) shouldBe "two" 43 | } 44 | 45 | it should "allow mock defined in fixture-context to be used during context initialization" in new TestSetupWithHandlerCalledDuringInitialization { 46 | when(mockedTrait.oneParamMethod).expects(2).returning("two") 47 | 48 | mockedTrait.oneParamMethod(2) shouldBe "two" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /scalatest/src/test/scala/eu/monniot/scala3mock/scalatest/SuiteScopeMockTest.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.scalatest 2 | 3 | import eu.monniot.scala3mock.scalatest.MockFactory 4 | import eu.monniot.scala3mock.mockable.TestTrait 5 | 6 | import org.scalatest.ParallelTestExecution 7 | import org.scalatest.flatspec.AnyFlatSpec 8 | import org.scalatest.matchers.should.Matchers 9 | 10 | /** Tests for mocks defined in suite scope (i.e. outside test case scope). */ 11 | class SuiteScopeMockTest extends AnyFlatSpec with Matchers with MockFactory { 12 | 13 | val mockWithoutExpectationsPredefined = mock[TestTrait] 14 | 15 | it should "allow to use mock defined suite scope" in { 16 | when(mockWithoutExpectationsPredefined.oneParamMethod) 17 | .expects(1) 18 | .returning("one") 19 | when(mockWithoutExpectationsPredefined.oneParamMethod) 20 | .expects(2) 21 | .returning("two") 22 | 23 | mockWithoutExpectationsPredefined.oneParamMethod(1) shouldBe "one" 24 | mockWithoutExpectationsPredefined.oneParamMethod(2) shouldBe "two" 25 | } 26 | 27 | it should "allow to use mock defined suite scope in more than one test case" in { 28 | when(mockWithoutExpectationsPredefined.oneParamMethod) 29 | .expects(3) 30 | .returning("three") 31 | 32 | mockWithoutExpectationsPredefined.oneParamMethod(3) shouldBe "three" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /scalatest/src/test/scala/eu/monniot/scala3mock/scalatest/SuiteScopePresetMockParallelTest.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.scalatest 2 | 3 | import eu.monniot.scala3mock.scalatest.MockFactory 4 | import eu.monniot.scala3mock.mockable.TestTrait 5 | 6 | import org.scalatest.ParallelTestExecution 7 | import org.scalatest.flatspec.AnyFlatSpec 8 | import org.scalatest.matchers.should.Matchers 9 | 10 | /** Tests for mocks defined in suite scope (i.e. outside test case scope) with 11 | * predefined expectations 12 | */ 13 | class SuiteScopePresetMockParallelTest 14 | extends AnyFlatSpec 15 | with Matchers 16 | with MockFactory { 17 | 18 | val mockWithExpectationsPredefined = mock[TestTrait] 19 | when(mockWithExpectationsPredefined.oneParamMethod) 20 | .expects(0) 21 | .returning("predefined") 22 | 23 | it should "allow to use mock defined suite scope with predefined expectations" in { 24 | when(mockWithExpectationsPredefined.oneParamMethod) 25 | .expects(1) 26 | .returning("one") 27 | 28 | mockWithExpectationsPredefined.oneParamMethod(0) shouldBe "predefined" 29 | mockWithExpectationsPredefined.oneParamMethod(1) shouldBe "one" 30 | } 31 | 32 | it should "keep predefined mock expectations" in { 33 | when(mockWithExpectationsPredefined.oneParamMethod) 34 | .expects(2) 35 | .returning("two") 36 | 37 | mockWithExpectationsPredefined.oneParamMethod(0) shouldBe "predefined" 38 | mockWithExpectationsPredefined.oneParamMethod(2) shouldBe "two" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /scalatest/src/test/scala/eu/monniot/scala3mock/scalatest/TestSuiteRunner.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.scalatest 2 | 3 | import org.scalatest._ 4 | import org.scalatest.events.{Event, TestFailed} 5 | import org.scalatest.matchers.should.Matchers 6 | 7 | import scala.language.postfixOps 8 | import scala.reflect.ClassTag 9 | 10 | trait TestSuiteRunner { this: Matchers => 11 | 12 | /** Executes single ScalaTest test case and returns its outcome (i.e. either 13 | * TestSucccess or TestFailure) 14 | */ 15 | def runTestCase[T <: Suite](suite: T): Event = { 16 | class TestReporter extends Reporter { 17 | var lastEvent: Option[Event] = None 18 | override def apply(e: Event): Unit = { lastEvent = Some(e) } 19 | } 20 | 21 | val reporter = new TestReporter 22 | suite.run(None, Args(reporter)) 23 | reporter.lastEvent.get 24 | } 25 | 26 | def getThrowable[ExnT <: Throwable: ClassTag](event: Event): ExnT = { 27 | event shouldBe a[TestFailed] 28 | 29 | val testCaseError = event.asInstanceOf[TestFailed].throwable.get 30 | testCaseError shouldBe a[ExnT] 31 | testCaseError.asInstanceOf[ExnT] 32 | } 33 | 34 | def getErrorMessage[ExnT <: Throwable: ClassTag](event: Event): String = { 35 | getThrowable[ExnT](event).getMessage() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /scalatest/src/test/scala/eu/monniot/scala3mock/scalatest/WithFixtureTest.scala: -------------------------------------------------------------------------------- 1 | package eu.monniot.scala3mock.scalatest 2 | 3 | import eu.monniot.scala3mock.scalatest.MockFactory 4 | import eu.monniot.scala3mock.mockable.TestTrait 5 | 6 | import org.scalatest._ 7 | import org.scalatest.events.TestSucceeded 8 | import org.scalatest.flatspec.AnyFlatSpec 9 | import org.scalatest.funsuite.AnyFunSuite 10 | import org.scalatest.matchers.should.Matchers 11 | 12 | class WithFixtureTest extends AnyFlatSpec with Matchers with TestSuiteRunner { 13 | 14 | object EventLogger { 15 | var events: List[String] = List.empty 16 | def logEvent(event: String) = { events = events :+ event } 17 | } 18 | 19 | trait SuiteWrapper extends SuiteMixin with TestSuite { 20 | abstract override def withFixture(test: NoArgTest): Outcome = { 21 | EventLogger.logEvent("SuiteWrapper setup") 22 | val outcome = super.withFixture(test) 23 | EventLogger.logEvent("SuiteWrapper cleanup") 24 | outcome 25 | } 26 | } 27 | 28 | class TestedSuite 29 | extends AnyFunSuite 30 | with SuiteWrapper 31 | with MockFactory 32 | with Matchers { 33 | test("execute block of code") { 34 | val mockedTrait = mock[TestTrait] 35 | when(mockedTrait.oneParamMethod).expects(1).onCall { (arg: Int) => 36 | EventLogger.logEvent("mock method called") 37 | "one" 38 | } 39 | 40 | mockedTrait.oneParamMethod(1) shouldBe "one" 41 | } 42 | } 43 | 44 | it can "be mixed together with other traits which override withFixture" in { 45 | val outcome = runTestCase[TestedSuite](new TestedSuite) 46 | outcome shouldBe a[TestSucceeded] 47 | EventLogger.events shouldBe List( 48 | "SuiteWrapper setup", 49 | "mock method called", 50 | "SuiteWrapper cleanup" 51 | ) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /website/core/Footer.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | class Footer extends React.Component { 4 | docUrl(doc, language) { 5 | const baseUrl = this.props.config.baseUrl; 6 | const docsUrl = this.props.config.docsUrl; 7 | const docsPart = `${docsUrl ? `${docsUrl}/` : ''}`; 8 | const langPart = `${language ? `${language}/` : ''}`; 9 | return `${baseUrl}${docsPart}${langPart}${doc}`; 10 | } 11 | 12 | pageUrl(doc, language) { 13 | const baseUrl = this.props.config.baseUrl; 14 | return baseUrl + (language ? `${language}/` : '') + doc; 15 | } 16 | 17 | render() { 18 | return ( 19 | 44 | ); 45 | } 46 | } 47 | 48 | module.exports = Footer; -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "MIT", 3 | "scripts": { 4 | "examples": "docusaurus-examples", 5 | "start": "docusaurus-start", 6 | "build": "docusaurus-build", 7 | "publish-gh-pages": "docusaurus-publish", 8 | "write-translations": "docusaurus-write-translations", 9 | "version": "docusaurus-version", 10 | "rename-version": "docusaurus-rename-version" 11 | }, 12 | "devDependencies": { 13 | "docusaurus": "^1.14.7" 14 | } 15 | } -------------------------------------------------------------------------------- /website/pages/en/index.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | 4 | const CompLibrary = require('../../core/CompLibrary.js'); 5 | 6 | const Container = CompLibrary.Container; 7 | const GridBlock = CompLibrary.GridBlock; 8 | 9 | class Index extends React.Component { 10 | render() { 11 | const { config: siteConfig, language = '' } = this.props; 12 | const { baseUrl, docsUrl } = siteConfig; 13 | const docsPart = `${docsUrl ? `${docsUrl}/` : ''}`; 14 | const langPart = `${language ? `${language}/` : ''}`; 15 | const docUrl = doc => `${baseUrl}${docsPart}${langPart}${doc}`; 16 | 17 | const Button = props => ( 18 | 23 | ); 24 | 25 | const Block = props => ( 26 | 30 | 35 | 36 | ); 37 | 38 | // TODO Change the format slightly to include some pictures, maybe ? 39 | // scalamock has some generic icons, but they do the job relatively well. 40 | const hookContent = [ 41 | "**Simple yet powerful**: Scala3Mock has very clean and concise syntax, reasonable defaults, powerful features and is fully type-safe.", 42 | "**Full Scala support**: Full support for Scala 3 features such as: Polymorphic methods, Operators, Overloaded methods, Type constraints, Implicits, and more.", 43 | "**Popular Test framework integration**: Scala3Mock can be easily used in ScalaTest and Munit testing frameworks.", 44 | "**Quick Start**: Quick introduction to Scala3Mock describing basic usage of this mocking framework", 45 | "**User Guide**: Complete Scala3Mock manual with lots of hints and examples", 46 | "**Open Source**: Scala3Mock is open source and licenced under the MIT licence" 47 | ]; 48 | 49 | return ( 50 |
51 | {/* Splash container */} 52 |
53 |
54 |
55 |
56 |

57 | {siteConfig.title} 58 | {siteConfig.tagline} 59 |

60 |
61 |
62 |
63 | 64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | 72 |
73 | 74 | {[{ 75 | title: 'Simple syntax', 76 | content: `\`\`\`scala 77 | withExpectations() { 78 | // Create mock Turtle object 79 | val mockedTurtle = mock[Turtle] 80 | 81 | // Set expectations 82 | (mockedTurtle.setPosition).expects(10.0, 10.0) 83 | (mockedTurtle.forward).expects(5.0) 84 | (mockedTurtle.getPosition).expects().returning(15.0, 10.0) 85 | 86 | // Exercise system under test 87 | drawLine(mockedTurtle, (10.0, 10.0), (15.0, 10.0)) 88 | } 89 | \`\`\`` 90 | }]} 91 | 92 | 93 | {[ 94 | { 95 | title: '', 96 | content: hookContent.join("\n\n"), 97 | //image: `${baseUrl}img/hello-printing.png`, 98 | imageAlign: 'right', 99 | } 100 | ]} 101 | 102 | 103 | {[{ 104 | title: '', 105 | content: 106 | "Scala3Mock is a soft fork of [ScalaMock](https://scalamock.org) and would not exists without it.
" + 107 | "We are very grateful to all the work that was put into ScalaMock over the years." 108 | }]} 109 |
110 |
111 |
112 | ); 113 | } 114 | } 115 | 116 | module.exports = Index; -------------------------------------------------------------------------------- /website/sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "docs": { 3 | "Overview": [ 4 | "getting-started" 5 | ], 6 | "User Guide": [ 7 | "user-guide/installation", 8 | "user-guide/features", 9 | "user-guide/matching", 10 | "user-guide/scalatest", 11 | "user-guide/cats", 12 | "user-guide/advanced-topics", 13 | "user-guide/faq" 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /website/siteConfig.js: -------------------------------------------------------------------------------- 1 | // See https://docusaurus.io/docs/site-config for all the possible 2 | // site configuration options. 3 | 4 | const siteConfig = { 5 | title: 'scala3mock', 6 | tagline: 'Native Scala 3 mocking system', 7 | url: 'https://francois.monniot.eu', 8 | baseUrl: '/scala3mock/', 9 | repoUrl: 'https://github.com/fmonniot/scala3mock', 10 | projectName: 'scala3mock', 11 | githubHost: 'github.com', 12 | // For top-level user or org sites, the organization is still the same. 13 | // e.g., for the https://JoelMarcey.github.io site, it would be set like... 14 | organizationName: 'fmonniot', 15 | 16 | // For no header links in the top nav bar -> headerLinks: [], 17 | headerLinks: [ 18 | {doc: 'getting-started', label: 'Docs'}, 19 | //{href: `/scala3mock/api/0.x/index.html`, label: 'API'}, 20 | {href: 'https://github.com/fmonniot/scala3mock', label: "GitHub", external: true} 21 | ], 22 | 23 | /* path to images for header/footer */ 24 | headerIcon: 'img/logo.svg', 25 | footerIcon: 'img/logo.svg', 26 | favicon: 'img/favicon.png', 27 | 28 | /* Colors for website */ 29 | colors: { 30 | primaryColor: '#d36d6f', 31 | secondaryColor: '#294066', 32 | }, 33 | 34 | // This copyright info is used in /core/Footer.js and blog RSS/Atom feeds. 35 | copyright: `Copyright (c) 2022-2023 François Monniot`, 36 | 37 | highlight: { 38 | // Highlight.js theme to use for syntax highlighting in code blocks. 39 | theme: 'github-gist', 40 | defaultLang: 'plaintext' 41 | }, 42 | 43 | // Add custom scripts here that would be placed in