or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assertions.mdindex.mdmocking.mdproperty-testing.mdtest-aspects.mdtest-environment.mdtest-execution.mdtest-specifications.md
tile.json

mocking.mddocs/

Mocking Framework

Complete mocking system for creating controlled test doubles with expectation-based verification.

Capabilities

Mock Class

Base class for creating service mocks with expectation management.

/**
 * Base class for service mocks
 * @tparam M - Service type (usually Has[Service])
 */
abstract class Mock[-M <: Has[_]] {
  /** Creates expectation for specific capability */
  def expects[I, A](capability: Capability[M, I, A]): Expectation[I]
  
  /** Composes this mock with another mock */
  def compose[M1 <: Has[_]](that: Mock[M1]): Mock[M with M1]
}

Expectation System

Expectation types for defining mock behavior and verification.

/**
 * Represents an expectation for mock method calls
 * @tparam I - Input parameter type
 */
sealed trait Expectation[-I] {
  /** Combines with another expectation (both must be satisfied) */
  def and[I1 <: I](that: Expectation[I1]): Expectation[I1]
  
  /** Alternative expectation (either can be satisfied) */
  def or[I1 <: I](that: Expectation[I1]): Expectation[I1]
  
  /** Repeats expectation for specified range of calls */
  def repeats(range: Range): Expectation[I]
  
  /** Specifies return value for matching calls */
  def returns[A](value: A): Expectation[I]
  
  /** Specifies effectful return value */
  def returnsM[R, E, A](effect: ZIO[R, E, A]): Expectation[I]
  
  /** Specifies that calls should throw error */
  def throws[E](error: E): Expectation[I]
  
  /** Specifies that calls should die with throwable */
  def dies(throwable: Throwable): Expectation[I]
}

object Expectation {
  /** Expectation that matches specific value */
  def value[A](assertion: Assertion[A]): Expectation[A]
  
  /** Expectation that matches any value */
  val unit: Expectation[Unit]
  
  /** Expectation that never matches */
  val never: Expectation[Any]
}

Capability System

Capability represents a mockable method or operation of a service.

/**
 * Represents a capability (method) that can be mocked
 * @tparam R - Service environment type
 * @tparam I - Input parameter type  
 * @tparam A - Return type
 */
case class Capability[R <: Has[_], I, A](name: String) {
  /** Creates expectation for this capability */
  def apply(assertion: Assertion[I]): Expectation[I]
}

object Capability {
  /** Creates capability with given name */
  def of[M <: Has[_], I, A](name: String): Capability[M, I, A]
}

Mock Result Types

Result types for specifying mock return values and behaviors.

/**
 * Result of a mock method call
 */
sealed trait Result

object Result {
  /** Successful result with value */
  def succeed[A](value: A): Result
  
  /** Failed result with error */
  def fail[E](error: E): Result  
  
  /** Death result with throwable */
  def die(throwable: Throwable): Result
  
  /** Empty/unit result */
  val unit: Result
}

Proxy System

System for creating dynamic proxy instances from mocks.

/**
 * Proxy factory for creating service instances from mocks
 */
object Proxy {
  /** Creates service proxy from mock */
  def make[R <: Has[_]](mock: Mock[R]): ULayer[R]
}

Built-in Service Mocks

Pre-built mocks for standard ZIO services.

/**
 * Mock for Clock service
 */
object MockClock extends Mock[Clock] {
  /** currentTime capability */
  val CurrentTime: Capability[Clock, Any, OffsetDateTime]
  
  /** currentDateTime capability */  
  val CurrentDateTime: Capability[Clock, Any, OffsetDateTime]
  
  /** nanoTime capability */
  val NanoTime: Capability[Clock, Any, Long]
  
  /** sleep capability */
  val Sleep: Capability[Clock, Duration, Unit]
}

/**
 * Mock for Console service
 */
object MockConsole extends Mock[Console] {
  /** putStr capability */
  val PutStr: Capability[Console, String, Unit]
  
  /** putStrLn capability */
  val PutStrLn: Capability[Console, String, Unit]
  
  /** putStrErr capability */
  val PutStrErr: Capability[Console, String, Unit]
  
  /** getStrLn capability */
  val GetStrLn: Capability[Console, Any, String]
}

/**
 * Mock for Random service  
 */
object MockRandom extends Mock[Random] {
  /** nextBoolean capability */
  val NextBoolean: Capability[Random, Any, Boolean]
  
  /** nextBytes capability */
  val NextBytes: Capability[Random, Int, Chunk[Byte]]
  
  /** nextDouble capability */
  val NextDouble: Capability[Random, Any, Double]
  
  /** nextFloat capability */
  val NextFloat: Capability[Random, Any, Float]
  
  /** nextInt capability */
  val NextInt: Capability[Random, Any, Int]
  
  /** nextIntBounded capability */
  val NextIntBounded: Capability[Random, Int, Int]
  
  /** nextLong capability */
  val NextLong: Capability[Random, Any, Long]
}

/**
 * Mock for System service
 */
object MockSystem extends Mock[System] {
  /** env capability */
  val Env: Capability[System, String, Option[String]]
  
  /** property capability */
  val Property: Capability[System, String, Option[String]]
  
  /** lineSeparator capability */
  val LineSeparator: Capability[System, Any, String]
}

Usage Examples

Basic Mock Usage

import zio.test._
import zio.test.mock._

// Define service interface
trait UserService {
  def getUser(id: String): Task[User]
  def createUser(user: User): Task[String]
}

// Create mock object
object MockUserService extends Mock[Has[UserService]] {
  object GetUser extends Effect[String, Throwable, User]
  object CreateUser extends Effect[User, Throwable, String]
  
  val compose: URLayer[Has[Proxy], Has[UserService]] =
    ZLayer.fromService { proxy =>
      new UserService {
        def getUser(id: String) = proxy(GetUser, id)
        def createUser(user: User) = proxy(CreateUser, user)
      }
    }
}

// Use in tests
test("user service mock") {
  val mockLayer = MockUserService.GetUser(
    Expectation.value(equalTo("123")).returns(User("123", "John"))
  )
  
  val program = for {
    userService <- ZIO.service[UserService]
    user        <- userService.getUser("123")
  } yield user
  
  assertM(program)(hasField("name", _.name, equalTo("John")))
    .provideLayer(mockLayer)
}

Complex Expectations

test("complex mock expectations") {
  val mockLayer = 
    MockUserService.GetUser(
      Expectation.value(equalTo("user1")).returns(User("user1", "Alice"))
    ) &&
    MockUserService.GetUser(
      Expectation.value(equalTo("user2")).returns(User("user2", "Bob"))  
    ) &&
    MockUserService.CreateUser(
      Expectation.value(hasField("name", _.name, startsWith("Test")))
        .returns("new-id-123")
        .repeats(1 to 3)
    )
    
  val program = for {
    service <- ZIO.service[UserService]
    user1   <- service.getUser("user1")
    user2   <- service.getUser("user2") 
    id1     <- service.createUser(User("", "Test User 1"))
    id2     <- service.createUser(User("", "Test User 2"))
  } yield (user1, user2, id1, id2)
  
  assertM(program) {
    case (u1, u2, id1, id2) =>
      assert(u1.name)(equalTo("Alice")) &&
      assert(u2.name)(equalTo("Bob")) &&
      assert(id1)(equalTo("new-id-123")) &&
      assert(id2)(equalTo("new-id-123"))
  }.provideLayer(mockLayer)
}

Service Mocks with Built-ins

test("built-in service mocks") {
  val clockMock = MockClock.CurrentTime(
    Expectation.unit.returns(OffsetDateTime.now())
  ) ++ MockClock.Sleep(
    Expectation.value(equalTo(1.second)).returns(())
  )
  
  val consoleMock = MockConsole.PutStrLn(
    Expectation.value(equalTo("Hello")).returns(())
  ) ++ MockConsole.GetStrLn(
    Expectation.unit.returns("input")
  )
  
  val program = for {
    _     <- clock.currentTime
    _     <- clock.sleep(1.second)
    _     <- putStrLn("Hello")
    input <- getStrLn
  } yield input
  
  assertM(program)(equalTo("input"))
    .provideLayer(clockMock ++ consoleMock)
}

Error and Failure Expectations

test("mock failures and errors") {
  val mockLayer = MockUserService.GetUser(
    Expectation.value(equalTo("missing")).throws(new RuntimeException("User not found"))
  ) ++ MockUserService.CreateUser(
    Expectation.value(hasField("name", _.name, isEmpty))
      .fails(ValidationError("Name cannot be empty"))
  )
  
  val program1 = ZIO.service[UserService].flatMap(_.getUser("missing"))
  val program2 = ZIO.service[UserService].flatMap(_.createUser(User("", "")))
  
  assertM(program1.run)(dies(hasMessage(containsString("User not found")))) &&
  assertM(program2.run)(fails(isSubtype[ValidationError]))
    .provideLayer(mockLayer)
}

Verification and Call Counting

test("call verification") {
  val mockLayer = MockUserService.GetUser(
    Expectation.value(anything).returns(User("", "")).repeats(2 to 4)
  )
  
  val program = for {
    service <- ZIO.service[UserService]  
    _       <- service.getUser("1")
    _       <- service.getUser("2")
    _       <- service.getUser("3")
  } yield ()
  
  assertM(program)(isUnit).provideLayer(mockLayer)
}