Skip to content

Commit

Permalink
Scala 3 (#490)
Browse files Browse the repository at this point in the history
* prepare build for scala 3

* resolve warnings for scala 3

* changed 2.13.8 to 2.13.11

* remove 2.11 and move scala-2 macro to scala-2 directory

* fix crossScalaVersions and tests

* tests prepared for scala 3

* mocks reimplemented with scala 3

* restricted vars support

* java workaround

* fix path dependent types in type constructors

* add scala 3 to workflow; remove scala_2.11

* Update build.sbt

Co-authored-by: Seth Tisue <[email protected]>

* Update .github/workflows/scala.yml

Co-authored-by: Seth Tisue <[email protected]>

* add comment about scala 3.3.0

* review fixes

* return java 11 in pipeline

* review fixes

---------

Co-authored-by: Seth Tisue <[email protected]>
  • Loading branch information
goshacodes and SethTisue authored Feb 13, 2024
1 parent 61e10c7 commit bb5d373
Show file tree
Hide file tree
Showing 74 changed files with 1,426 additions and 453 deletions.
28 changes: 14 additions & 14 deletions .github/workflows/scala.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,6 @@ on:
branches: [ master ]

jobs:
scala_2_11:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: 11
distribution: 'adopt'
- name: Run tests
run: sbt ++2.11.12 test

scala_2_12:

runs-on: ubuntu-latest
Expand Down Expand Up @@ -48,3 +34,17 @@ jobs:
distribution: 'adopt'
- name: Run tests
run: sbt ++2.13.12 test

scala_3:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: 11
distribution: 'adopt'
- name: Run tests
run: sbt ++3.3.0 test
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ A more complete example is on our [Quickstart](http://scalamock.org/quick-start/
* Mock and Stub support
* Macro Mocks and JVM Proxy Mocks
* Scala.js support
* built for Scala 2.11, 2.12, 2.13
* 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

## Using ScalaMock

Expand All @@ -70,6 +71,53 @@ libraryDependencies += Seq("org.scalamock" %% "scalamock" % "5.2.0" % Test,
"org.scalatest" %% "scalatest" % "3.2.0" % Test)
```

## Scala 3 Migration Notes

1. Type should be specified for methods with by-name parameters
```scala 3
trait TestTrait:
def byNameParam(x: => Int): String

val t = mock[TestTrait]

// this one no longer compiles
(t.byNameParam _).expects(*).returns("")

// this one should be used instead
(t.byNameParam(_: Int)).expects(*).returns("")
```

2.
* Not initialized vars are not supported anymore, use `scala.compiletime.uninitialized` instead
* Vars are **not mockable** anymore

```scala 3
trait X:
var y: Int // No longer compiles

var y: Int = scala.compile.uninitialized // Should be used instead
```


Mocking of non-abstract java classes is not available without workaround

```java
public class JavaClass {
public int simpleMethod(String b) { return 4; }
}

```

```scala 3
val m = mock[JavaClass] // No longer compiles

class JavaClassExtended extends JavaClass

val mm = mock[JavaClassExtended] // should be used instead
```



## Documentation

For usage in Maven or Gradle, integration with Specs2, and more example examples see the [User Guide](http://scalamock.org/user-guide/)
Expand Down
52 changes: 34 additions & 18 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
import sbtcrossproject.CrossPlugin.autoImport.crossProject

ThisBuild / scalaVersion := "2.11.12"
ThisBuild / crossScalaVersions := Seq("2.11.12", "2.12.18", "2.13.12")
//ThisBuild / scalaJSUseRhino := true

lazy val scalatest = Def.setting("org.scalatest" %%% "scalatest" % "3.2.17")
lazy val specs2 = Def.setting("org.specs2" %%% "specs2-core" % "4.10.6")
lazy val specs2 = Def.setting("org.specs2" %%% "specs2-core" % "4.20.2")

val commonSettings = Defaults.coreDefaultSettings ++ Seq(
Compile / unmanagedSourceDirectories ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2L, minor)) =>
Some(baseDirectory.value.getParentFile / s"shared/src/main/scala-2.$minor")
case _ =>
None
}
},
scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature", "-Xcheckinit", "-target:jvm-1.8")
/**
* Symbol.newClass is marked experimental, so we should use @experimental annotation in every test suite.
* 3.3.0 has a bug so we can omit this annotation
*/
scalaVersion := "3.3.0",
scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature", "-release:8")
)

lazy val scalamock = crossProject(JSPlatform, JVMPlatform) in file(".") settings(
commonSettings,
crossScalaSettings,
name := "scalamock",
Compile / packageBin / publishArtifact := true,
Compile / packageDoc / publishArtifact := true,
Expand All @@ -29,21 +23,43 @@ lazy val scalamock = crossProject(JSPlatform, JVMPlatform) in file(".") settings
Compile / doc / scalacOptions ++= Opts.doc.title("ScalaMock") ++
Opts.doc.version(version.value) ++ Seq("-doc-root-content", "rootdoc.txt", "-version"),
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
scalatest.value % Optional,
specs2.value % Optional
)
)

lazy val `scalamock-js` = scalamock.js
lazy val `scalamock-jvm` = scalamock.jvm

lazy val examples = project in file("examples") settings(
commonSettings,
crossScalaSettings,
name := "ScalaMock Examples",
publish / skip := true,
libraryDependencies ++= Seq(
scalatest.value % Test,
specs2.value % Test
)
) dependsOn scalamock.jvm

def crossScalaSettings = {
def addDirsByScalaVersion(path: String): Def.Initialize[Seq[sbt.File]] =
scalaVersion.zip(baseDirectory) { case (v, base) =>
CrossVersion.partialVersion(v) match {
case Some((v, _)) if Set(2L, 3L).contains(v) =>
Seq(base / path / s"scala-$v")
case _ =>
Seq.empty
}
}
Seq(
crossScalaVersions := Seq("2.12.18", "2.13.12", scalaVersion.value),
Compile / unmanagedSourceDirectories ++= addDirsByScalaVersion("src/main").value,
Test / unmanagedSourceDirectories ++= addDirsByScalaVersion("src/test").value,
libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, _)) =>
Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value)
case _ =>
Seq.empty
}
}
)
}
6 changes: 3 additions & 3 deletions examples/src/test/scala/com/example/ControllerTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ class ControllerTest extends AnyFunSuite with MockFactory {
inSequence {
inAnyOrder {
(() => mockTurtle.penUp()).expects()
(mockTurtle.getPosition _).expects().returning(0.0, 0.0)
(mockTurtle.getAngle _).expects().returning(0.0)
(() => mockTurtle.getPosition).expects().returning(0.0, 0.0)
(() => mockTurtle.getAngle).expects().returning(0.0)
}
(mockTurtle.turn _).expects(~(Pi / 4))
(mockTurtle.forward _).expects(~sqrt(2.0))
(mockTurtle.getAngle _).expects().returning(Pi / 4)
(() => mockTurtle.getAngle).expects().returning(Pi / 4)
(mockTurtle.turn _).expects(~(-Pi / 4))
(() => mockTurtle.penDown()).expects()
(mockTurtle.forward _).expects(1.0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ class HigherOrderFunctionsTest extends AnyFreeSpec with MockFactory {
val f = mockFunction[Int, String]

inSequence {
f expects (1) returning "one" once;
f expects (2) returning "two" once;
f expects (3) returning "three" once;
f.expects(1).returning("one").once();
f.expects(2).returning("two").once();
f.expects(3).returning("three").once();
}

assertResult(Seq("one", "two", "three")) {
Expand All @@ -61,10 +61,10 @@ class HigherOrderFunctionsTest extends AnyFreeSpec with MockFactory {
val f = mockFunction[String, Int, String]

inSequence {
f expects("initial", 0) returning "intermediate one" once;
f expects("intermediate one", 1) returning "intermediate two" once;
f expects("intermediate two", 2) returning "intermediate three" once;
f expects("intermediate three", 3) returning "final" once;
f.expects("initial", 0).returning("intermediate one").once();
f.expects("intermediate one", 1).returning("intermediate two"). once();
f.expects("intermediate two", 2).returning("intermediate three").once();
f.expects("intermediate three", 3).returning("final").once();
}

assertResult("final") {
Expand Down
6 changes: 3 additions & 3 deletions examples/src/test/scala/com/example/OrderSpecification.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class OrderSpecification extends Specification {
"remove inventory when in stock" in new MockContext {
val mockWarehouse = mock[Warehouse]
inSequence {
(mockWarehouse.hasInventory _).expects("Talisker", 50).returning(true).once
(mockWarehouse.remove _).expects("Talisker", 50).once
(mockWarehouse.hasInventory _).expects("Talisker", 50).returning(true).once()
(mockWarehouse.remove _).expects("Talisker", 50).once()
}
val order = new Order("Talisker", 50)
order.fill(mockWarehouse)
Expand All @@ -43,7 +43,7 @@ class OrderSpecification extends Specification {

"remove nothing when out of stock" in new MockContext {
val mockWarehouse = mock[Warehouse]
(mockWarehouse.hasInventory _).expects(*, *).returns(false).once
(mockWarehouse.hasInventory _).expects(*, *).returns(false).once()
val order = new Order("Talisker", 50)
order.fill(mockWarehouse)
order.isFilled must beFalse
Expand Down
4 changes: 2 additions & 2 deletions examples/src/test/scala/com/example/OrderTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class OrderTest extends AnyWordSpec with MockFactory {
"remove inventory" in {
val mockWarehouse = mock[Warehouse]
inSequence {
(mockWarehouse.hasInventory _) expects ("Talisker", 50) returning true
(mockWarehouse.remove _) expects ("Talisker", 50) once
(mockWarehouse.hasInventory _).expects ("Talisker", 50).returning(true)
(mockWarehouse.remove _).expects("Talisker", 50).once()
}

val order = new Order("Talisker", 50)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class ReallySimpleExampleTest extends AnyFunSuite with MockFactory {
test("WithVariableParameters") {
val australianFormat = mock[Formatter]

(australianFormat.format _).expects(*).onCall { s: String => s"G'day $s" }.twice()
(australianFormat.format _).expects(*).onCall { (s: String) => s"G'day $s" }.twice()

Greetings.sayHello("Wendy", australianFormat)
Greetings.sayHello("Gray", australianFormat)
Expand All @@ -62,10 +62,10 @@ class ReallySimpleExampleTest extends AnyFunSuite with MockFactory {
}

// argAssert fails early
(formatter.format _).expects(argAssert(assertTeamNatsu _)).onCall { s: String => s"Yo $s" }.once()
(formatter.format _).expects(argAssert(assertTeamNatsu _)).onCall { (s: String) => s"Yo $s" }.once()

// 'where' verifies at the end of the test
(formatter.format _).expects(where { s: String => teamNatsu contains(s) }).onCall { s: String => s"Yo $s" }.twice()
(formatter.format _).expects(where { (s: String) => teamNatsu contains(s) }).onCall { (s: String) => s"Yo $s" }.twice()

Greetings.sayHello("Carla", formatter)
Greetings.sayHello("Happy", formatter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ class ControllerTest extends AnyFunSuite with MockFactory {

inSequence {
inAnyOrder {
(mockTurtle.getPosition _).when().returns(0.0, 0.0)
(mockTurtle.getAngle _).when().returns(0.0).once()
(() => mockTurtle.getPosition).when().returns(0.0, 0.0)
(() => mockTurtle.getAngle).when().returns(0.0).once()
}
(mockTurtle.getAngle _).when().returns(Pi / 4)
(() => mockTurtle.getAngle).when().returns(Pi / 4)
}

controller.drawLine((1.0, 1.0), (2.0, 1.0))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,18 @@ class HigherOrderFunctionsTest extends AnyFreeSpec with MockFactory {
"testMap" in {
val f = stubFunction[Int, String]

f when (1) returns "one"
f when (2) returns "two"
f when (3) returns "three"
f.when(1).returns("one")
f.when(2).returns("two")
f.when(3).returns("three")

assertResult(Seq("one", "two", "three")) {
Seq(1, 2, 3) map f
}

inSequence {
f verify (1) once;
f verify (2) once;
f verify (3) once;
f.verify(1).once();
f.verify(2).once();
f.verify(3).once();
}
}

Expand Down Expand Up @@ -74,10 +74,10 @@ class HigherOrderFunctionsTest extends AnyFreeSpec with MockFactory {
}

inSequence {
f verify("initial", 0) once;
f verify("intermediate one", 1) once;
f verify("intermediate two", 2) once;
f verify("intermediate three", 3) once;
f.verify("initial", 0).once();
f.verify("intermediate one", 1).once();
f.verify("intermediate two", 2).once();
f.verify("intermediate three", 3).once();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class OrderTest extends AnyWordSpec with MockFactory {
order.fill(mockWarehouse)

assert(order.isFilled)
(mockWarehouse.remove _) verify ("Talisker", 50) once
(mockWarehouse.remove _).verify("Talisker", 50).once()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.paulbutcher.test.mock

import com.paulbutcher.test.TestTrait
import org.scalamock.function.FunctionAdapter1
import org.scalatest.matchers.should.Matchers
import org.scalamock.scalatest.MockFactory
import org.scalatest.freespec.AnyFreeSpec

class ByNameParametersTest extends AnyFreeSpec with MockFactory with Matchers {

autoVerify = false

"cope with methods with by name parameters" in {
withExpectations {
val m = mock[TestTrait]
(m.byNameParam _).expects(*).returning("it worked")
assertResult("it worked") { m.byNameParam(42) }
}
}

//! TODO - find a way to make this less ugly
"match methods with by name parameters" in {
withExpectations {
val m = mock[TestTrait]
val f: (=> Int) => Boolean = { x => x == 1 && x == 2 }
((m.byNameParam _): (=> Int) => String).expects(new FunctionAdapter1(f)).returning("it works")
var y = 0
assertResult("it works") { m.byNameParam { y += 1; y } }
}
}
}
Loading

0 comments on commit bb5d373

Please sign in to comment.