scala3mock

scala3mock

  • Docs
  • GitHub

›User Guide

Overview

  • Getting Started

User Guide

  • Features
  • Argument matching
  • ScalaTest Integration
  • Cats Integration
  • Advanced Topics
  • FAQ

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, and
  • player can have any name, any email, any country, as long as its id 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)
← FeaturesScalaTest Integration →
  • Any matching
  • Predicate matching
    • Example 1
    • Example 2
  • Epsilon matching
scala3mock
Docs
Getting StartedFAQ
Copyright (c) 2022-2023 François Monniot