specs2 version cross compiled for Scala 2.12, 2.13 and 3.1 #1070
Replies: 2 comments 1 reply
-
Hi @etorreborre, thank you for the fantastic work to support Scala 3! 👍 👍 👍 👍 In the previous version (4.x), we used Scope to define variables, avoiding redundant declarations in multiple places within our unit tests. With its absence in version 5.x, we're unsure how to achieve a similar outcome. Could you provide guidance on how to adapt our code to version 5.x? Alternatively, do you recommend any better patterns or practices for achieving the same result in the new version? Here is one of our tests with version 4.x: class LoggerSpec extends Specification:
"Logger" >> {
"GIVEN logger exists" >> {
"WHEN invoke info method" >> {
"THEN return a unit" >> new LoggerTestScope {
// codes
}
}
"WHEN invoke error method" >> {
"THEN return a unit" >> new LoggerTestScope {
// codes
}
}
}
}
private trait LoggerTestScope extends Scope:
val metaInfo: Map[String, Json] = Map("meta" -> "METADATA".asJson)
val current: Instant = Instant.parse("2020-12-01T12:12:12Z")
given Clock[IO] = new Clock[IO] {
override def realTime: IO[FiniteDuration] = IO(current.getEpochSecond.seconds)
override def applicative: Applicative[IO] = ???
override def monotonic: IO[FiniteDuration] = ???
} Thank you for your assistance! 👍 |
Beta Was this translation helpful? Give feedback.
-
Hi @gonBorn. class LoggerSpec extends Specification:
"Logger" >> {
import LoggerSetup.*
"GIVEN logger exists" >> {
"WHEN invoke info method" >> {
"THEN return a unit" >> {
// codes
}
}
"WHEN invoke error method" >> {
"THEN return a unit" >> {
// codes
}
}
}
}
object LoggerSetup:
val metaInfo: Map[String, Json] = Map("meta" -> "METADATA".asJson)
val current: Instant = Instant.parse("2020-12-01T12:12:12Z")
given Clock[IO] = new Clock[IO] {
override def realTime: IO[FiniteDuration] = IO(current.getEpochSecond.seconds)
override def applicative: Applicative[IO] = ???
override def monotonic: IO[FiniteDuration] = ???
} If some mutation is involved you can do the following, to get a fresh state for each test: class LoggerSpec extends org.specs2.mutable.Specification:
"Logger" >> {
"GIVEN logger exists" >> {
"WHEN invoke info method" >> {
"THEN return a unit" >> {
val setup = new LoggerSetup; import setup.*
logged = "message" :: logged
logged === List("message")
}
}
"WHEN invoke error method" >> {
"THEN return a unit" >> {
val setup = new LoggerSetup; import setup.*
logged = "message2" :: logged
logged === List("message2")
}
}
}
}
class LoggerSetup:
var logged: List[String] = Nil It is a bit more verbose since you need at least one more line per test. Here is another variation (still more verbose than using class LoggerSpec extends org.specs2.mutable.Specification:
"Logger" >> {
"GIVEN logger exists" >> {
"WHEN invoke info method" >> {
"THEN return a unit" >> withLoggerSetup { (setup: LoggerSetup) =>
import setup.*
logged = "message" :: logged
logged === List("message")
}
}
"WHEN invoke error method" >> {
"THEN return a unit" >> withLoggerSetup { (setup: LoggerSetup) =>
import setup.*
logged = "message2" :: logged
logged === List("message2")
}
}
}
}
def withLoggerSetup[T](f: LoggerSetup => T): T = f(new LoggerSetup)
class LoggerSetup:
var logged: List[String] = Nil Then, if you really wish to keep the import org.specs2.matcher.*
import org.specs2.execute.*
trait Scope
object Scope {
given scopeAsResult[S <: Scope]: AsResult[S] = new AsResult[S]:
def asResult(t: =>S): Result = AsResult.safely { Result.resultOrSuccess(t) }
implicit inline def scopeToResult(t: =>Scope): Result = AsResult(t)
}
class LoggerSpec extends org.specs2.mutable.Specification:
"Logger" >> {
"GIVEN logger exists" >> {
"WHEN invoke info method" >> {
"THEN return a unit" >> new LoggerTestScope {
logged = "message" :: logged
logged === List("message")
}
}
"WHEN invoke error method" >> {
"THEN return a unit" >> new LoggerTestScope {
logged = "message2" :: logged
logged === List("message2")
}
}
}
}
private trait LoggerTestScope extends Scope:
var logged: List[String] = Nil This will work as long as you make sure that every failed expectation throws an exception. Finally I must say that my own preference would be nowadays:
I think that in the case of your example I would have:
Then in my tests simply go: // makeLogger is a helper function somewhere which makes a logger with a fixed clock
val logger = makeLogger("2020-12-01T12:12:12Z")
logger.info("message")
etc... I hope that helps, don't hesitate to ask if you find any more issues. I'm super glad to see people finally able to move to Scala 3! |
Beta Was this translation helpful? Give feedback.
-
This version of specs2 provides an interim support for Scala 2.12, 2.13 and 3.1.
In order to be compatible with all Scala versions some features had to be removed (like macro-based features) and you might have to adapt your code. For example:
value must matcher
orvalue must not(matcher)
, and avoid expressions likea must not be something
. This is in line with the new specs2 5.x versionbeLike
orcontain
) might require some type annotationsThis version is provided as a best effort for bridging the gap between Scala 2 and Scala 3 but it is recommended that you switch to a specs2 5.x version (only available for Scala 3) if you can because this will be the actively maintained version.
Thanks for reporting any issue with this version, I will try to address it if it is blocking your migration process.
This discussion was created from the release specs2 version cross compiled for Scala 2.12, 2.13 and 3.1.
Beta Was this translation helpful? Give feedback.
All reactions