Announcing ZIO Test support in scalamock classic (released in 7.5.0)

Earlier this year we built an integration between scalamock classic and ZIO Test.
After writing more than 400 tests with this integration on a large production codebase, we contributed the work to open source — and it is now part of scalamock itself.

As of version 7.5.0, scalamock classic has first-class support for ZIO Test.

Release notes: scalamock 7.5.0

Why this was needed

The now-deprecated zio-mock was a source of frustration for many developers and also a blocker for Scala 3 migration.
The combination of scalamock classic and ZIO Test turned out to be a simpler, more reliable alternative that improves the developer experience.

Improvements

One key addition is the ability to check that effects are actually executed, not just that methods were called.

Example buggy code:

def setGreeting(userId: Int): UIO[Unit] =
  for {
    userName <- userService.getUserName(userId)
    greeting = s"Hello, $userName!"
    _ = userService.setGreeting(greeting) // effect not executed
  } yield ()

Classic scalamock would report this as “method invoked” and the test would pass.
With the new integration, the test fails with a clear error:

Expected:
  UserService.setGreeting(Hello, Agent Smith!) once (never called - UNSATISFIED)

If you’re curious how this works, check out the macro implementation here: CheckEffectInvocationMacros .

Another improvement: mocks are provided via ZLayer in a way that avoids shared state between tests, making them more reliable.

How to get started

Add the dependency:

libraryDependencies += "org.scalamock" %% "scalamock-zio" % "7.5.0" % Test

Then write your tests extending ScalamockZIOSpec:

import org.scalamock.ziotest.*
import zio.test.*
import zio.*

// ScalamockZIOSpec provides everything needed to work with scalamock in zio-test
object ApiServiceSpec extends ScalamockZIOSpec {

  override def spec: Spec[TestEnvironment, Any] =
    suite("ApiServiceSpec")(
      test("return greeting")(
        for {
          // Setup expectations — how mock should be called and what it returns
          _ <- ZIO.serviceWith[UserService] { mock =>
            (mock.getUserName _).expects(4).returnsZIO("Agent Smith")
          }
          // Call code under test
          result <- ZIO.serviceWithZIO[ApiService](_.getGreeting(4))
        } yield assertTrue(result == "Hello, Agent Smith!")
      )
    ).provide(ApiService.layer, mock[UserService]) // Provide required mock for the test
}

More details in the documentation: ZIO Test | scalamock

4 Likes