ilusm.dev

mock

Test mocking and stubbing - create stub functions that return a fixed value or delegate to a function, wrap any function in a spy that records every call, replace object fields temporarily with restore support, inspect call counts and arguments, return a sequence of values across calls, throw on call, and collect fakes into a test module for bulk verification.

Load with: use mock

What this module does

mock gives you the classic test-double toolkit. Stubs replace real functions with controlled return values. Spies wrap real functions and record calls without changing behaviour. Object stubs (mocst) replace a field on an object and give you a restore handle. The test module (moctm) collects named fakes and can verify all of them were called at least once.

Quick example

use mock

# Stub - always returns "ok"
m = mock.fn("ok")
prn(m.fn("anything"))   # "ok"

# Computed stub
m2 = mock.fn(\(x) x * 2)
prn(m2.fn(5))   # 10

# Spy - records calls, delegates to original
real = \(x) x + 1
sp = mock.spy(real)
sp.fn(10)
sp.fn(20)
prn(mock.count(sp))         # 2
prn(mock.last(sp))          # 20
prn(mock.with(sp, 10))      # tru

# Stub object field
db = {query: \(q) "real result"}
s = mock.stub(db, "query", \(q) "mocked")
prn(db.query("SELECT 1"))   # "mocked"
db = mock.rst(s)            # restore original
prn(db.query("SELECT 1"))   # "real result"

# Sequential return values
seq = mock.seq(["first", "second", "third"])
prn(seq.fn(nil))  # "first"
prn(seq.fn(nil))  # "second"
prn(seq.fn(nil))  # "third" (repeats last)

# Error mock
em = mock.err("not implemented")
try(\() em.fn(nil))  # throws "not implemented"

# Test module with verify
tm = mock.tm()
mock.fake(tm, "save", \(x) tru)
# ... call save ...
mock.verify(tm)  # throws if any fake was never called

Functions

Creating mocks

mocfn(rv) / mock.fn(rv)

Creates a stub. If rv is a function it is called with the argument; otherwise rv is returned directly. Returns {fn, calls}.

mocsp(orig) / mock.spy(orig)

Creates a spy wrapping orig. Calls are forwarded to orig and recorded. Returns {fn, calls, orig}.

mocse(rets) / mock.seq(rets)

Creates a stub that returns values from rets in sequence. Repeats the last value once the list is exhausted.

mocer(msg) / mock.err(msg)

Creates a stub that always throws msg when called.

Object stubbing

mocst(obj, key, val) / mock.stub(obj, key, val)

Replaces obj[key] with val and returns a stub handle {obj, key, orig}.

mocrst(stub) / mock.rst(stub)

Restores the original value and returns the updated object.

Assertions

moccn(m) / mock.count(m)

Returns the number of recorded calls.

mocca(m) / mock.any(m)

Returns tru if the mock was called at least once.

mocnc(m, n) / mock.n(m, n)

Returns tru if the mock was called exactly n times.

moccw(m, args) / mock.with(m, args)

Returns tru if the mock was ever called with args.

moclc(m) / mock.last(m)

Returns the argument from the most recent call, or nil.

mocfc(m) / mock.first(m)

Returns the argument from the first call, or nil.

mocrr(m) / mock.clr(m)

Returns a copy of the mock with the call history cleared.

Test module

moctm() / mock.tm()

Creates a test module registry {fakes}.

mocfk(tm, name, rv) / mock.fake(tm, name, rv)

Registers a named fake in the test module. Returns the fake's .fn.

mocgt(tm, name) / mock.get(tm, name)

Retrieves a registered fake by name.

mocva(tm) / mock.verify(tm)

Throws an error naming the first fake that was never called. Returns tru if all fakes were called.

Notes

  • Requires trl.