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:
pointsequal to 100, andplayercan have anyname, anyemail, anycountry, as long as itsidis 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)