liveBook · Manning

Mocking

As explained in Functional Design and Architecture: Examples in Haskell

A mock is a configurable test-double implementation that simulates a real component or service without invoking the real one.

Mocking replaces a real dependency with a configurable test implementation. It lets tests run without physical hardware, native functions, or other unpredictable external subsystems, while deliberately producing values, failures, and edge cases that expose weaknesses in business logic. section 4.2.4 section 15.1

The enabling design choice is an interface boundary. Business logic calls a service handle, free-monad language, or other interface rather than a concrete implementation; a test then supplies a mock with the same operations. This preserves the application logic while changing the environment in which it runs. section 4.3 section 15.2.1

A useful mock should make the test's controlled conditions explicit:

  • Return fake domain values for expected calls, including absent or invalid results that real systems may produce. section 4.2.4
  • Provide configured values that steer evaluation into a particular branch of business logic. section 15.2.2
  • Vary values across calls when a scenario needs a sequence rather than one fixed response. section 15.2.2
  • Optionally reject impossible configured values so that an invalid test setup fails clearly. section 15.2.2
  • Record calls when interaction itself matters, using mutable state only inside the test double. section 4.2.4

Mocking a hardware service

A device-assembly test substitutes both device creation and part lookup. The fake service builds a device from a two-component template, returns fake thermometer and pressure parts for known indexes, and returns Nothing for a requested third part. The test verifies that missing data is handled rather than causing a crash, without connecting any physical device. section 4.2.4

Listing 4.12 A test that uses a mocked hardware service
it "Getting absent device part" $ do
  
  let testDef =
        [ ComponentDef "t1" thermometer1Passp
        , ComponentDef "p1" pressure1Passp
        ]
  
  device  <- makeDevice mockedHardwareService testDef          #1
  
  mpPart1 <- getDevicePart mockedHardwareService "t1" device   #2
  mpPart2 <- getDevicePart mockedHardwareService "p1" device   #2
  mpPart3 <- getDevicePart mockedHardwareService "t2" device   #3
  
  case (mpPart1, mpPart2, mpPart3) of                          #4
    (Just _, Just _, Nothing) -> pure ()
    _ -> fail "Device is assembled incorrectly."               #5

This test shows dependency injection at each service call and checks known parts alongside an intentionally absent one. section 4.2.4

Free monads support the same separation through interpreters. A live random interpreter invokes random generation; a test interpreter can return a fixed value, validate it against the requested range, or consume a mutable sequence. This makes a multi-step scenario repeatable: configure 3 and then 2 to force the game-selection branch and choose Dwarf Fortress. section 15.2.2

Figure 15.3 Free monads, mocks, and white-box tests

The diagram situates mock interpreters between a free-monad interface and white-box tests, alongside real interpreters. section 15.2.2

Mocks are not a substitute for architecture. They depend on low coupling and interfaces that cannot be bypassed by business logic. Bare effects inside business logic undermine this arrangement because hidden side effects remain outside the mockable boundary. When dependencies are isolated, mocks support white-box unit tests; pure functions often need no mocking because their behavior is deterministic. section 15.1 section 15.2.1 section 15.3

How Functional Design and Architecture: Examples in Haskell covers it

How deep it goes
Demonstrates service-handle and free-monad mocks, including fixed, validated, sequential, and call-counting behavior.
Examples
Uses a missing hardware component and a configured random game picker that selects Dwarf Fortress.
What it assumes
Assumes familiarity with interfaces and impure dependencies while contrasting them with deterministic pure functions.
What you can do after
Apply it via service handles or free-monad interpreters to run controlled tests without real dependencies.
How it connects
Connects mocking to dependency injection, dependency inversion, Service Handle pattern, free monads, interfaces, and white-box testing.

Also explained in

Compare how 21 books explain mocking →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Functional Design and Architecture: Examples in Haskell. The section links point to the parts of the book it draws on.