scala3mock

scala3mock

  • Docs
  • GitHub

›Overview

Overview

  • Getting Started

User Guide

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

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.

Next →
  • Install
  • Basic Usage
  • Throwing an exception in a mock
  • Dynamic return value
  • Verifying arguments dynamically
  • Further reading
scala3mock
Docs
Getting StartedFAQ
Copyright (c) 2022-2023 François Monniot