Argument matching
Scala3Mock support three types of matching mocked functions arguments:
- any matching, also called wildcards
- epsilon matching,
- predicate matching
In this page we will make use of a few common declarations, so let's get them out of the way
import eu.monniot.scala3mock.ScalaMocks.*
val mockedFunction = mockFunction[String, Any, Unit]
// mockedFunction: MockFunction2[String, Any, Unit] = MockFunction2
Any matching
Any matching are defined using the MatchAny
class, or more commonly its *
alias. For example:
mockedFunction.expects("", *).anyNumberOfTimes
// res0: CallHandler2[String, Any, Unit] = MockFunction2(, *) any number of times (called 4 times)
will match any of the following:
mockedFunction("", "")
mockedFunction("", 1.0)
mockedFunction("", null)
mockedFunction("", Object())
Predicate matching
More complicated argument matching can be implemented by using where
to provide your own logic.
trait MatchPredicate:
def where[Arg1](predicate: (Arg1) => Boolean)
def where[Arg1, Arg2](predicate: (Arg1, Arg2) => Boolean)
Example 1
In this example we will use the following PlayerLeaderboard
interface.
case class Player(id: Long, name: String, emailAddress: String, country: String)
trait PlayerLeaderBoard {
def addPointsForPlayer(player: Player, points: Int): Unit
}
val leaderBoardMock = mock[PlayerLeaderBoard]
// leaderBoardMock: PlayerLeaderBoard = repl.MdocSession$MdocApp$PlayerLeaderBoardMock$1@11a1d890
Now imagine that we want to put an expectation that addPointsForPlayer
is called with:
points
equal to 100, andplayer
can have anyname
, anyemail
, anycountry
, as long as itsid
is 789.
Achieving that can be done using the where
predicate:
when(leaderBoardMock.addPointsForPlayer).expects(where {
(player: Player, points: Int) => player.id == 789 && points == 100
})
// res5: CallHandler2[Nothing, Nothing, Unit] = <matching.md#L64> PlayerLeaderBoard.addPointsForPlayer<function1> once (never called - UNSATISFIED)
Example 2
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.
val mockedFunction2 = mockFunction[Double, Double, Unit] // (Double, Double) => Unit
// mockedFunction2: MockFunction2[Double, Double, Unit] = MockFunction2 // (Double, Double) => Unit
mockedFunction2.expects(where { _ < _ }) // expects that arg1 < arg2
// res6: CallHandler2[Nothing, Nothing, Unit] = MockFunction2<function1> once (never called - UNSATISFIED)
Epsilon matching
Epsilon matching is useful when dealing with floating point values. An epsilon match is specified with the MatchEpsilon
class, or more commonly its ~
alias:
val mockedFunction3 = mockFunction[Double, Unit] // (Double, Double) => Unit
// mockedFunction3: MockFunction1[Double, Unit] = MockFunction1 // (Double, Double) => Unit
mockedFunction3.expects(~42.0).anyNumberOfTimes
// res7: CallHandler1[Double, Unit] = MockFunction1(~42.0) any number of times (called 3 times)
will match:
mockedFunction3(42.0)
mockedFunction3(42.0001)
mockedFunction3(41.9999)
but will not match:
mockedFunction3(43.0)
mockedFunction3(42.1)
// eu.monniot.scala3mock.MockExpectationFailed: Unexpected call: MockFunction1(43.0)
//
// Expected:
// inAnyOrder {
// MockFunction2(, *) any number of times (called 4 times)
// <matching.md#L64> PlayerLeaderBoard.addPointsForPlayer<function1> once (never called - UNSATISFIED)
// MockFunction2<function1> once (never called - UNSATISFIED)
// MockFunction1(~42.0) any number of times (called 3 times)
// }
//
// Actual:
// MockFunction2(, )
// MockFunction2(, 1.0)
// MockFunction2(, null)
// MockFunction2(, java.lang.Object@1031dd6a)
// MockFunction1(42.0)
// MockFunction1(42.0001)
// MockFunction1(41.9999)
// MockFunction1(43.0)
// at repl.MdocSession$MdocApp$$anon$17.newExpectationException(matching.md:12)
// at repl.MdocSession$MdocApp$$anon$17.newExpectationException(matching.md:11)
// at eu.monniot.scala3mock.context.MockContext.reportUnexpectedCall(MockContext.scala:29)
// at eu.monniot.scala3mock.context.MockContext.reportUnexpectedCall$(MockContext.scala:7)
// at repl.MdocSession$MdocApp$$anon$17.reportUnexpectedCall(matching.md:8)
// at eu.monniot.scala3mock.functions.MockFunction.onUnexpected(MockFunction.scala:12)
// at eu.monniot.scala3mock.functions.MockFunction.onUnexpected$(MockFunction.scala:8)
// at eu.monniot.scala3mock.functions.MockFunction1.onUnexpected(MockFunction.scala:24)
// at eu.monniot.scala3mock.functions.FakeFunction.handle$$anonfun$1(FakeFunction.scala:18)
// at scala.Option.getOrElse(Option.scala:201)
// at eu.monniot.scala3mock.functions.FakeFunction.handle(FakeFunction.scala:18)
// at eu.monniot.scala3mock.functions.FakeFunction.handle$(FakeFunction.scala:6)
// at eu.monniot.scala3mock.functions.FakeFunction1.handle(FakeFunction.scala:35)
// at eu.monniot.scala3mock.functions.FakeFunction1.apply(FakeFunction.scala:39)
// at scala.Function1.apply$mcVD$sp(Function1.scala:71)
// at scala.Function1.apply$mcVD$sp$(Function1.scala:71)
// at eu.monniot.scala3mock.functions.FakeFunction1.apply$mcVD$sp(FakeFunction.scala:35)
// at repl.MdocSession$MdocApp.$init$$$anonfun$3(matching.md:120)