ScalaMock Documentation

repository·series/7.x·Indexed 19 days ago

https://github.com/scalamock/scalamock

A native Scala mocking framework for creating mock objects for Scala classes and traits. It uses compile-time generation to create stubs and mocks, providing a core module and specialized integrations for ZIO and Cats Effect. Features include type-safe argument matching, call verification, and support for both Scala 2 and Scala 3 syntax.

Tokens
4.3K
Snippets
14
Records
16
Agent score
68%

What's inside ScalaMock

  1. How stub and mock object generation works

    series/7.x

    Scalamock uses compile-time generation to create stubs and mocks. For any provided trait or class definition, Scalamock generates an implementation that stores state internally. This state is updated whenever an expectation is set or a method is invoked.

    There are two primary internal approaches depending on the API used:

    1. New Stubs: Uses a StubbedMethod[Args, Result] for each method. If a method has multiple arguments, they are internally handled as a tuple. To simplify generation, these are stored as StubbedMethod[Any, Any] and cast to the correct type at runtime.
    2. Classic API: Uses specific function types based on the exact number of arguments (e.g., MockFunction1, MockFunction2, up to MockFunction22). This allows the public API to provide type-safe argument matching (e.g., expects(*, *)).
    trait Foo:
      def oneArg(x: Int): Int
      def twoArgs(x: Int, y: String): String
      def curried(x: Int)(y: String): Int
    
    val foo = stub[Foo]
  2. Install ScalaMock via sbt

    series/7.x

    To use ScalaMock in your Scala project, add the following dependencies to your libraryDependencies in your build.sbt file. You can choose the core module or specific integrations for ZIO or Cats Effect.

    Note: Replace <latest version in badge> with the current version of ScalaMock (e.g., 7.5.5).

    libraryDependencies ++= Seq(
      // core module
      "org.scalamock" %% "scalamock" % "7.5.5",
      // zio integration
      "org.scalamock" %% "scalamock-zio" % "7.5.5",
      // cats-effect integration
      "org.scalamock" %% "scalamock-cats-effect" % "7.5.5"
    )
  3. How StubbedMethod works with Scala 2 and Scala 3

    series/7.x

    ScalaMock provides different syntaxes for accessing StubbedMethod depending on your Scala version.

    Given a stubbed trait:

    trait Foo:
      def foo0: Int
      def foo(x: Int): Int
      def fooBar(bar: Boolean, baz: String): String
    
    val foo = stub[Foo]

    Scala 3 Syntax

    Methods are accessed directly. For methods with multiple arguments, the argument type A is a tuple.

    val foo0Stubbed: StubbedMethod[Unit, Int] = () => foo.foo0
    val fooStubbed: StubbedMethod[Int, Int] = foo.foo
    val fooBarStubbed: StubbedMethod[(Boolean, String), String] = foo.fooBar

    Scala 2 Syntax

    You must use the method reference syntax (_) to obtain the StubbedMethod.

    val foo0Stubbed: StubbedMethod[Unit, Int] = () => foo.foo0
    val fooStubbed: StubbedMethod[Int, Int] = foo.foo _
    val fooBarStubbed: StubbedMethod[(Boolean, String), String] = foo.fooBar _
    // Scala 3
    foo.fooBar.returnsWith("value")
    
    // Scala 2
    (foo.fooBar _).returnsWith("value")
  4. Use ZIO Assertions as Mock Parameters

    series/7.x

    You can use zio.test.Assertion directly as a parameter matcher in ScalaMock expectations. This allows you to leverage the full power of ZIO Test assertions when validating arguments passed to mocked methods.

    Default Behavior (Ordered)

    By default, converting a zio.test.Assertion[A] to a MockParameter[A] uses argAssert internally. This means it is sensitive to call order and does not work with inAnyOrder.

    Unordered Calls

    If you need to use a ZIO assertion within an inAnyOrder expectation, you must use the allowAnyOrderSyntax extension. This provides the .allowUnorderedCalls method on your assertion.

    Example Comparison

    If you have a UserService with a getName method:

    Using standard ZIO Assertion (Ordered):

    (mock.getName _).expects(argAssert("request")(_.id shouldBe 4)).returnsZIO("Agent Smith")

    Using ScalaMock Matcher (Equivalent):

    (mock.getName _).expects(hasField("id", _.id, equalTo(4))).returnsZIO("Agent Smith")
    // Using a ZIO assertion directly in an expectation
    (mock.getName _).expects(assertion).returnsZIO("Agent Smith")
    
    // For inAnyOrder, use allowUnorderedCalls
    (mock.getName _).expects(assertion.allowUnorderedCalls).inAnyOrder.returnsZIO("Agent Smith")
  5. Configure stubbed ZIO methods within a ZIO context

    series/7.x

    When using ScalaMock with ZIO, you can use StubbedZIOMethod to configure stubbed behavior (results, failures, etc.) and inspect call statistics directly within a ZIO effect. This is useful for integrating stub configuration into your test suites that run within the ZIO runtime.

    Key Capabilities

    • Set Results: Define what a method returns based on arguments or call count using ZIO effects.
    • Simulate Failures: Use failsWith or diesWith to simulate error scenarios.
    • Inspect Calls: Retrieve the number of times a method was called (timesZIO) or the list of arguments used (callsZIO) as ZIO effects.

    Scala 2 vs Scala 3 Syntax

    In Scala 3, you can access stubbed methods directly via the stub object. In Scala 2, you must use the underscore syntax (_) to convert the method to a function/stubbed method.

    // Scala 3 Example
    for
      _ <- foo.bar.returnsZIO((x, y) => ZIO.succeed(1))
      _ <- foo.bar(1, "foo")
      calls <- foo.bar.callsZIO
    yield calls == List((1, "foo"))
    
    // Scala 2 Example
    for {
      _ <- (foo.bar _).returnsZIO((x, y) => ZIO.succeed(1))
      _ <- (foo.bar _)(1, "foo")
      calls <- (foo.bar _).callsZIO
    } yield calls == List((1, "foo"))
  6. Configure stubbed behavior within a Cats Effect IO context

    series/7.x

    When using ScalaMock with Cats Effect, you can use StubbedIOMethod to configure stubbed method behavior within an IO context. This allows you to perform stubbing setup (like returnsIO) and verification (like callsIO) inside for comprehensions or other IO workflows.

    To access these methods, the CatsEffectStubs interface provides implicit conversions from a selected method to a StubbedIOMethod (or StubbedMethodIO).

    Note on Scala versions:

    • In Scala 3, you can access the stubbed method directly (e.g., foo.bar).
    • In Scala 2, you must use the underscore syntax to pass the method as a function (e.g., foo.bar _).
    // Scala 3 Example
    for
      _ <- foo.bar.returnsIO((x, y) => IO(1))
    yield ()
    
    // Scala 2 Example
    for {
      _ <- (foo.bar _).returnsIO((x, y) => IO(1))
    } yield ()
  7. Resetting a stub using stub$macro$clear()

    series/7.x

    Every generated stub includes a stub$macro$clear() method. Calling this method clears all recorded calls and results for every method within that stub.

    This is useful when you want to reuse the same stub instance across multiple test cases within a suite. However, if you use this approach, your test cases must run sequentially to avoid state leakage between tests.

    // Conceptually, the generated code looks like this:
    val foo = new Foo:
      def stub$macro$clear(): Unit =
        stub$oneArg$0.clear()
        stub$twoArgs$1.clear()
        stub$curried$2.clear()
  8. Debugging stub failures with toString representations

    series/7.x

    To assist with debugging failed test cases and reviewing call logs, each StubbedMethod has an overridden toString method. This string is constructed from a unique stub index, the type name, and the method signature.

    In Scala 3, the string representation is derived from the compiler. An example representation looks like: <stub-0> Foo.oneArg(x: Int)Int

  9. Inspect stubbed ZIO method calls and statistics

    series/7.x

    Use these methods to verify how many times a stubbed method was called and with what arguments. These return ZIO effects.

    MethodDescription
    callsZIOReturns a UIO[List[A]] containing the arguments used in every call.
    timesZIOReturns a UIO[Int] representing the total number of times the method was called.
    timesZIO(args: A)Returns a UIO[Int] representing the number of times the method was called with the specific arguments args.
    // Scala 3
    for {
      _ <- foo.bar.returnsZIO(_ => ZIO.succeed(5))
      _ <- foo.bar(1, "foo")
      _ <- foo.bar(2, "bar")
      calls <- foo.bar.callsZIO
      count <- foo.bar.timesZIO
      specificCount <- foo.bar.timesZIO((1, "foo"))
    } yield (
      calls == List((1, "foo"), (2, "bar")) && 
      count == 2 && 
      specificCount == 1
    )
  10. Define behaviors for mocked methods using StubbedMethod

    series/7.x

    The StubbedMethod[A, R] trait allows you to define how a mocked method should behave when called. A represents the type of arguments (often a tuple for multiple arguments) and R represents the return type.

    In Scala 3, you can access a StubbedMethod directly from the stubbed trait. In Scala 2, you typically use the underscore syntax (e.g., foo.bar _) to convert a method to a StubbedMethod.

    Setting Return Values

    • returns(f: A => R): Sets a function that determines the result based on the input arguments.
    • returnsWith(value: => R): Sets a constant return value (evaluated lazily).
    • returnsOnCall(f: Int => R): Sets a result based on the call count (starting from 1).

    Verifying Calls

    • times: Int: Returns the total number of times the method was executed.
    • times(args: A): Int: Returns the number of times the method was executed with specific arguments.
    • calls: List[A]: Returns a list of all arguments used in each call, in order of execution.
    • isBefore(other): Returns true if this method was called before the other method.
    • isAfter(other): Returns true if this method was called after the other method.
    // Scala 3 usage
    foo.fooBar.returns {
      case (true, "bar") => "true"
      case _ => "false"
    }
    
    // Scala 2 usage
    (foo.fooBar _).returns {
      case (true, "bar") => "true"
      case _ => "false"
    }
  11. Set results for stubbed methods using IO

    series/7.x

    Use these methods to define what a stubbed method returns when it is called, specifically when the return type R is an IO type.

    MethodDescription
    returnsIO(f: A => R)Sets the result using a function that takes arguments A and returns R. Returns IO[Unit].
    returnsIOWith(value: => R)Sets the result to a specific value. Returns IO[Unit].
    succeedsWith[RR](value: => RR)Sets the method to succeed with a value (requires implicit conversion to R). Returns IO[Unit].
    returnsIOOnCall(f: Int => R)Sets the result based on the call number (starting from 1). Returns IO[Unit].
    // Scala 3
    for
      _ <- foo.bar.returnsIO((x, y) => IO(1)) // Using function
      _ <- foo.bar.returnsIOWith(IO(1))        // Using value
      _ <- foo.bar.succeedsWith(1)             // Using succeedsWith
      _ <- foo.bar.returnsIOOnCall {            // Using call number
        case 1 | 2 => IO(0)
        case _ => IO(1)
      }
    yield ()