Getting Started
This article describes how to get started with Scala3Mock. Because it is only an introduction, only the basics usage are described. For a comprehensive guide, see the User Guide.
If you are coming from ScalaMock for Scala 2, there are a few changes you need to be aware of. See the FAQ.
Install
To get started with SBT and ScalaTest, add the following dependencies to your build.sbt:
libraryDependencies += "eu.monniot" %% "scala3mock" % "0.6.6" % Test
libraryDependencies += "eu.monniot" %% "scala3mock-scalatest" % "0.6.6" % Test
While some testing framework integration exists, Scala3Mock at its core do not require one. You can learn more about the various testing framework integration by going to to the dedicated page in the user guide. If there is no page, it means no special integration has been written yet (or it is not required).
Basic Usage
:info: The scala code you'll see in this example is being compiled and should work as is. Note that a snippet may depend on previous snippets.
First let's assume we have some functionality to test. We are going to be very imaginative and use a Greeting
example. To simplify the implementation, it will have a sayHello
function which take a name and a formatter, and print the formatted greeting.
object Greetings {
sealed trait Formatter { def format(s: String): String }
object English extends Formatter { def format(s: String) = s"Hello $s" }
object French extends Formatter { def format(s: String) = s"Bonjour $s" }
object Japanese extends Formatter { def format(s: String) = s"こんにちは $s" }
def sayHello(name: String, formatter: Formatter): Unit =
println(formatter.format(name))
}
// Let's import the content of our object for future snippets
import Greetings.*
With Scala3Mock, we can test the interaction between the sayHello
function and a given Formatter
. To be able to mock things, we need an implicit MockContext
. The core library provides a withExpectations
function that provide you with one.
For example, we can check that the name is actually being used and that our formatter is called only once.
To create a new mock, place the following contents into a Scala file within your project:
import eu.monniot.scala3mock.ScalaMocks.*
withExpectations() {
val formatter = mock[Formatter]
when(formatter.format)
.expects("Mr Bond")
.returns("Ah, Mr Bond. I've been expecting you")
.once
sayHello("Mr Bond", formatter)
}
// Ah, Mr Bond. I've been expecting you
Note that you do not have to specify the argument specifically but can instead accept any arguments to a mock with AnyMatcher
or its *
alias.
withExpectations() {
val formatter = mock[Formatter]
when(formatter.format)
.expects(*)
.returns("Ah, Mr Bond. I've been expecting you")
.once
sayHello("Mr Bond", formatter)
}
// Ah, Mr Bond. I've been expecting you
Other basic available features that you may find useful are included below.
Throwing an exception in a mock
withExpectations() {
val brokenFormatter = mock[Formatter]
when(brokenFormatter.format)
.expects(*)
.throwing(new NullPointerException)
.anyNumberOfTimes
try Greetings.sayHello("Erza", brokenFormatter)
catch {
case e: NullPointerException => println("expected")
}
}
// expected
Dynamic return value
withExpectations() {
val australianFormat = mock[Formatter]
when(australianFormat.format)
.expects(*)
.onCall { (s: String) => s"G'day $s" }
.twice
Greetings.sayHello("Wendy", australianFormat)
Greetings.sayHello("Gray", australianFormat)
}
// G'day Wendy
// G'day Gray
Verifying arguments dynamically
withExpectations() {
val teamNatsu = Set("Natsu", "Lucy", "Happy", "Erza", "Gray", "Wendy", "Carla")
val formatter = mock[Formatter]
def assertTeamNatsu(s: String): Unit = {
assert(teamNatsu.contains(s))
}
// 'where' verifies at the end of the test
when(formatter.format)
.expects(where { (s: String) => teamNatsu contains(s) })
.onCall { (s: String) => s"Yo $s" }
.twice
Greetings.sayHello("Carla", formatter)
Greetings.sayHello("Lucy", formatter)
}
// Yo Carla
// Yo Lucy
Further reading
Scala3Mock has some powerful features. The example below show some of them.
withExpectations(verifyAfterRun=false) {
val httpClient = mock[HttpClient]
val counterMock = mock[Counter]
when(httpClient.sendRequest).expects(Method.GET, *, *).twice
when(httpClient.sendRequest).expects(Method.POST, "http://scalamock.org", *).noMoreThanOnce
when(httpClient.sendRequest).expects(Method.POST, "http://example.com", *).returning(Http.NotFound)
when(counterMock.increment).expects(*).onCall { (arg: Int) => arg + 1}
when(() => counterMock.decrement).expects().onCall { () => throw RuntimeException("here") }
}
// res5: CallHandler0[Int] = <getting-started.md#L149> Counter.decrement() once (never called - UNSATISFIED)
Please read the User Guide for more details.