Skip to content

Commit

Permalink
Merge pull request #562 from goshacodes/new_api
Browse files Browse the repository at this point in the history
New API with ZIO and CE integration
  • Loading branch information
goshacodes authored Jan 9, 2025
2 parents 89a8d63 + 2d383c9 commit 1817e1f
Show file tree
Hide file tree
Showing 159 changed files with 2,523 additions and 201 deletions.
235 changes: 225 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,219 @@ Native Scala mocking.

Official website: [scalamock.org](https://scalamock.org/)

## Examples

## Scala 3 new API (since 7.0.0)
Scalamock internals and new API relies on Scala 3 experimental API, so prerequisites are:
```scala
scalaVersion := "3.4.2" // or higher
Test / scalacOptions += "-experimental"
```

### Dependencies

```scala
libraryDependencies ++= Seq(
// core module
"org.scalamock" %% "scalamock" % "7.0.0",
// zio integration
"org.scalamock" %% "scalamock-zio" % "7.0.0",
// cats-effect integration
"org.scalamock" %% "scalamock-cats-effect" % "7.0.0"
)
```

### Basic API

```scala 3
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import org.scalamock.stubs.{Stub, Stubs}

trait Foo:
def foo: Int
def foo1(x: Int): Int
def foo2(x: Int, y: Int): Int

class MySpec extends AnyFunSpec, Matchers, Stubs:

test("no args"):
val foo: Stub[Foo] = stub[Foo]

foo.foo.returns(10)
foo.foo shouldBe 10 // method returns set result
foo.times shouldBe 1 // number of times method called

test("one arg"):
val foo: Stub[Foo] = stub[Foo]

foo.foo1.returns:
case 1 => 2
case _ => 0

foo.foo1(1) shouldBe 2
foo.foo1(2) shouldBe 0
foo.times shouldBe 2
foo.calls shouldBe List(1, 2) // get arguments


test("two args"):
val foo: Stub[Foo] = stub[Foo]

foo.foo2.returns:
case (0, 0) => 1
case _ => 0

foo.foo2(0, 0) shouldBe 1
foo.foo2(2, 3) shouldBe 0
foo.times shouldBe 2
foo.calls shouldBe List((0, 0), (2, 3)) // get arguments
foo.times((0, 0)) shouldBe 1 // get number of times arguments caught

```

### ZIO API
Dependencies:
```scala
libraryDependencies ++= {
val zioVersion = "2.1.14"
Seq(
"dev.zio" %%% "zio" % zioVersion,
"dev.zio" %%% "zio-test" % zioVersion % Test,
"dev.zio" %%% "zio-test-sbt" % zioVersion % Test
)
}
```

Examples:
```scala 3
import zio.*
import zio.test.*
import org.scalamock.stubs.{Stub, ZIOStubs}

trait Foo:
def foo: UIO[Int]
def foo1(x: Int): UIO[Int]
def foo2(x: Int, y: Int): IO[String, Int]

class MySpec extends ZIOSpecDefault, ZIOStubs:

override def spec: Spec[TestEnvironment & Scope, Any] =
suite("check expectations with zio")(
test("no args"):
val foo: Stub[Foo] = stub[Foo]
for
_ <- foo.foo.returnsZIO(ZIO.succeed(10))
_ <- foo.foo.repeatN(10)
times <- foo.foo.timesZIO
result = assertTrue(times == 11)
yield result,
test("one arg"):
val foo: Stub[Foo] = stub[Foo]
for
_ <- foo.foo1.returnsZIO:
case 1 => ZIO.succeed(1)
case _ => ZIO.succeed(0)
one <- foo.foo1(1)
two <- foo.foo1(2)
times <- foo.foo1.timesZIO
calls <- foo.foo1.callsZIO
result = assertTrue(
times == 2,
one == 1,
two == 0,
calls == List(1, 2)
)
yield result,
test("two args"):
val foo: Stub[Foo] = stub[Foo]
for
_ <- foo.foo2.returnsZIO:
case (0, 0) => ZIO.succeed(1)
case _ => ZIO.succeed(0)
one <- foo.foo2(0, 0)
two <- foo.foo2(2, 2)
times <- foo.foo2.timesZIO
calls <- foo.foo2.callsZIO
twoZerosTimes <- foo.foo2.timesZIO((0, 0))
result = assertTrue(
times == 2,
calls == List((0, 0), (2, 2)),
twoZerosTimes == 1,
one == 1,
two == 0
)
yield result
)
```


### Cats Effect API
Dependencies:
```scala
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-effect" % "3.5.7",
"org.typelevel" %% "munit-cats-effect" % "2.0.0" % Test
)
```

Examples:
```scala 3
import cats.effect.IO
import munit.CatsEffectSuite
import org.scalamock.stubs.{Stub, CatsEffectStubs}

trait Foo:
def foo: UIO[Int]
def foo1(x: Int): UIO[Int]
def foo2(x: Int, y: Int): IO[String, Int]

class MySpec extends CatsEffectSuite, CatsEffectStubs:
test("no args"):
val foo: Stub[Foo] = stub[Foo]
val times =
for
_ <- foo.foo.returnsIO(IO(10))
_ <- foo.foo
_ <- foo.foo
_ <- foo.foo
times <- foo.foo.timesIO
yield times

assertIO(times == 2)

test("one arg"):
val foo: Stub[Foo] = stub[Foo]
val result =
for
_ <- foo.foo1.returnsIO:
case 1 => IO(1)
case _ => IO(0)
one <- foo.foo1(1)
two <- foo.foo1(2)
times <- foo.foo1.timesIO
calls <- foo.foo1.callsIO
yield (times, one, two, calls)

assertIO(result, (2, 1, 0, List(1, 2))

test("two args"):
val foo: Stub[Foo] = stub[Foo]
val result = for
_ <- foo.foo2.returnsIO:
case (0, 0) => IO(1)
case _ => IO(0)
one <- foo.foo2(0, 0)
two <- foo.foo2(2, 2)
times <- foo.foo2.timesIO
calls <- foo.foo2.callsIO
twoZerosTimes <- foo.foo2.timesIO((0, 0))
yield (times, calls, twoZerosTimes, one, two)

assertIO(result, (2, List((0, 0), (2, 2)), 1, 1, 0))

```

## Old API examples

### Expectations-First Style

Expand All @@ -14,9 +226,9 @@ test("drawline interaction with turtle") {
val m = mock[Turtle]

// Set expectations
(m.setPosition _).expects(10.0, 10.0)
(m.forward _).expects(5.0)
(m.getPosition _).expects().returning(15.0, 10.0)
m.setPosition.expects(10.0, 10.0).returns(10.0, 10.0)
m.forward.expects(5.0).returns(())
m.getPosition.expects().returns(15.0, 10.0)

// Exercise System Under Test
drawLine(m, (10.0, 10.0), (15.0, 10.0))
Expand All @@ -31,14 +243,14 @@ test("drawline interaction with turtle") {
val m = stub[Turtle]

// Setup return values
(m.getPosition _).when().returns(15.0, 10.0)
m.getPosition.when().returns(15.0, 10.0)

// Exercise System Under Test
drawLine(m, (10.0, 10.0), (15.0, 10.0))

// Verify expectations met
(m.setPosition _).verify(10.0, 10.0)
(m.forward _).verify(5.0)
m.setPosition.verify(10.0, 10.0)
m.forward.verify(5.0)
}
```

Expand All @@ -59,6 +271,7 @@ A more complete example is on our [Quickstart](http://scalamock.org/quick-start/
* built for Scala 2.12, 2.13, 3
* Scala 2.10 support was included up to ScalaMock 4.2.0
* Scala 2.11 support was included up to ScalaMock 5.2.0
* Scala 2.12 and 2.13 support was included up to ScalaMock 6.1.1

## Using ScalaMock

Expand All @@ -67,8 +280,10 @@ Artefacts are published to Maven Central and Sonatype OSS.
For ScalaTest, to use ScalaMock in your Tests, add the following to your `build.sbt`:

```scala
libraryDependencies += Seq("org.scalamock" %% "scalamock" % "5.2.0" % Test,
"org.scalatest" %% "scalatest" % "3.2.0" % Test)
libraryDependencies += Seq(
"org.scalamock" %% "scalamock" % "5.2.0" % Test,
"org.scalatest" %% "scalatest" % "3.2.0" % Test
)
```

## Scala 3 Migration Notes
Expand Down Expand Up @@ -117,7 +332,7 @@ val mm = mock[JavaClassExtended] // should be used instead
```

3.
* Mockito makes use of Symbol.newClass which is marked as experimental; to avoid having to add the `@experimental`
* Scala makes use of Symbol.newClass which is marked as experimental; to avoid having to add the `@experimental`
attribute everywhere in tests, you can add the `Test / scalacOptions += "-experimental"` to your build. Note
that this option is only available in scala 3.4.0+

Expand Down
56 changes: 54 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,22 @@ val commonSettings = Defaults.coreDefaultSettings ++ Seq(
)
)

lazy val scalamock = crossProject(JSPlatform, JVMPlatform) in file(".") settings(
lazy val root = project.in(file("."))
.settings(
publish / skip := true
)
.aggregate(
scalamock.jvm,
scalamock.js,
zio.jvm,
zio.js,
`cats-effect`.jvm,
`cats-effect`.js
)

lazy val scalamock = crossProject(JSPlatform, JVMPlatform)
.in(file("core"))
.settings(
commonSettings,
name := "scalamock",
Compile / packageBin / publishArtifact := true,
Expand All @@ -25,10 +40,46 @@ lazy val scalamock = crossProject(JSPlatform, JVMPlatform) in file(".") settings
libraryDependencies ++= Seq(
scalatest.value % Optional,
specs2.value % Optional
),
)

lazy val zio = crossProject(JSPlatform, JVMPlatform)
.in(file("zio"))
.settings(
name := "scalamock-zio",
commonSettings,
Compile / packageBin / publishArtifact := true,
Compile / packageDoc / publishArtifact := true,
Compile / packageSrc / publishArtifact := true,
Test / publishArtifact := false,
libraryDependencies ++= {
val zioVersion = "2.1.14"
Seq(
"dev.zio" %%% "zio" % zioVersion,
"dev.zio" %%% "zio-test" % zioVersion % Test,
"dev.zio" %%% "zio-test-sbt" % zioVersion % Test
)
}
)
.dependsOn(scalamock)

lazy val `cats-effect` = crossProject(JSPlatform, JVMPlatform)
.in(file("cats-effect"))
.settings(
name := "scalamock-cats-effect",
commonSettings,
Compile / packageBin / publishArtifact := true,
Compile / packageDoc / publishArtifact := true,
Compile / packageSrc / publishArtifact := true,
Test / publishArtifact := false,
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-effect" % "3.5.7",
"org.typelevel" %% "munit-cats-effect" % "2.0.0" % Test
)
)
.dependsOn(scalamock)

lazy val examples = project in file("examples") settings(
lazy val examples = project in file("core/examples") settings(
commonSettings,
name := "ScalaMock Examples",
publish / skip := true,
Expand All @@ -52,3 +103,4 @@ inThisBuild(
)
)
)

Loading

0 comments on commit 1817e1f

Please sign in to comment.