- Tired of choosing between mocks and stubs?
- Tired of thrown exceptions when expectations not met?
- Or you just don't like 'mocks/stubs' approach entirely?
Then you've come to the right place, meet backstub - stubbing library for Scala 3 inspired by scalamock and compatible with any testing framework out of the box
How it works:
- Setup cleanup after each test-case
- Setup expected results based on input arguments per suit or per test-case
- After stubs were used - get the data passed through the method and verify
Should be mixed with your test-suite, to provide clean-up API
package backstub
trait Stubs:
given stubs: CreatedStubs = CreatedStubs()
def resetStubs(): Unit = stubs.clearAll()
Using it as simple as:
import backstub.*
class MySpec extends munit.FunSuite with Stubs:
override def afterEach(context: AfterEach) =
resetStubs()
Generates a stub. Without expectations setup just throws NotImplementedError
import backstub.*
trait Foo:
def zeroArgs: String
def oneArg(x: Int): Int
def moreArgs(x: Int, y: String): Option[String]
stub[Foo]
Will generate you:
new Foo:
def zeroArgs: String = ???
def oneArg(x: Int): Int = ???
def moreArgs(x: Int, y: String): Option[String] = ???
Compile time configuration allowing you to setup stub methods results.
Expectation on method could be set only once, so if you want to differentiate calls - use different data.
This also generates a collector for your calls.
import backstub.*
val foo = stub[Foo]:
Expect[Foo]
.method(_.oneArg).returns:
case 1 => 2
case 2 => 3
.method(_.moreArgs).returns(_ => None)
Will generate you:
new Foo:
def zeroArgs: String = ???
val calls$oneArg$1 = new AtomicReference[List[Int]](Nil)
def oneArg(x: Int): Int =
calls$oneArg$1.getAndUpdate(_ :+ x)
x match
case 1 => 2
case 2 => 3
val calls$moreArgs$2 = new AtomicReference[List[(Int, String)]](Nil)
def moreArgs(x: Int, y: String): Option[String] =
calls$moreArgs$2.getAndUpdate(_ :+ (x, y))
None
Also expectations can be provided via an inline given:
import backstub.*
inline given Expect[Foo] = Expect[Foo]
.method(_.oneArg).returns:
case 1 => 2
case 2 => 3
.method(_.moreArgs).returns(_ => None)
val foo = stub[Foo]
backstub won't verify anything for you, it only returns the data.
It gives you 2 extension methods for your stubs:
- calls gives you the data
- times gives you the number of times a method was called
import backstub.*
val foo = stub[Foo]
foo.oneArg(1)
foo.oneArg(2)
foo.times(_.oneArg) // 2
foo.calls(_.oneArg) // List(1, 2)
foo.twoArgs(5, "foo")
foo.times(_.twoArgs) // 1
foo.calls(_.twoArgs) // List((5, "foo"))
To set expectations for overloaded methods - method type should be specified
import backstub.*
trait Overloaded:
def overloaded(x: Int, y: Boolean): Int
def overloaded(x: String): Boolean
def overloaded: String
val overloaded = stub[Overloaded]:
Expect[Overloaded]:
.method(_.overloaded: String).returnsOnly("foo")
.method(_.overloaded: String => Boolean).returns(_ => true)
.method(_.overloaded: (Int, Boolean) => Int).returns((x, y) => x)
Model - SessionCheckService.scala
Suite - SessionCheckServiceSpec.scala
Only basic functionality is supported by now.
It won't block your stub generation, but you won't set up an expectation:
- Methods with type parameters are not supported
- Curried methods are not supported
- Probably something else is not supported, you can always open an issue to discuss and a PR
If this library makes you so incredibly happy, that you really want to donate - contact me using link in github profile
scala tests, scala test, testing in scala, scala testing, scalatest, specs2, scalamock, zio mock, mockito scala