Mock
Mock: deterministic stand-ins for the world outside the program: the filesystem, environment, and console. Networking mocks live under Mock.Net.
These are STATEFUL: Mock.FS.File(path, content) writes into a store the real FS.File then reads back, so a test can write and read again, and clear() undoes it. That state is global and lives until cleared, which is what makes hook ordering and write/read round trips testable, and also what makes two tests able to interfere.
Use with FS.File = MyFake { ... } do ... end for a replacement limited to one block. Use mocks when a test needs state shared across several calls, and clear that state between tests.
Import with using Mock. Everything here is test-only: the mock operations and the stand-in records (Mock.Files, Mock.Env, Mock.Clock) alike are allowed in spec files and the REPL, and need --allow-mocks anywhere else.
type Reader
A stand-in's read hook: a path in, its content or None out.
type Lookup
An environment stand-in's lookup hook: a name in, its value or None out.
type Tick
A clock stand-in's reading hook: nothing in, the instant out.
Variants
Block(DateTime)
type Writer
An environment stand-in's WRITE hook: the name and the value a program set. A Mock.Env is an immutable record, so a write cannot land in its vars, and it must not reach the real environment, or a test would leak a variable into the next one and into anything the suite starts. The hook is how a test observes the write instead: record it, assert on it, or ignore it, which is what None does.
module Mock
record Files
A stand-in for the FS.File capability, for with FS.File = .... Unlike the Mock.* functions below it holds no global state, needs no clear(), and cannot leak past its block. What it cannot do is change: write then read back is not something a value does, so a test needing that round trip uses Mock.FS.
with FS.File = Mock.Files { files: {"kex.toml": "name = \"demo\""} } do
assert(loadConfig() == "demo")
end
onRead takes over when a test needs an ANSWER rather than a fixture: content derived from the path, a failure on the third call, a record of what was asked for. It is consulted first and its None means "no such file", so a callback can model absence too.
Fields
filesMap<FS.FilePath, String>optionalonReadReader?optional
Implements FS.File.
Functions
cannedRead
cannedRead(path)
Named apart from read: this.read(path) would bind to the capability's own read : FilePath -> String?, not to this function.
read
read(path)
readBytes
readBytes(path)
readLines
readLines(path)
feed
feed(path)
size
size(path)
exists?
exists?(path)
file?
file?(path)
directory?
directory?(path)
absolute
absolute(path)
canonical
canonical(path)
symlink?
symlink?(path)
info
info(path)
open
open(path, mode)
A fake is a value, so there is nowhere for a write to go. Refusing is the honest answer and the useful one: a test that did not expect a write sees it fail rather than silently succeed.
write
write(path, content)
writeBytes
writeBytes(path, content)
append
append(path, content)
delete
delete(path)
copy
copy(src, dst)
rename
rename(src, dst)
changePermission
changePermission(path, permissions)
changeMode
changeMode(path, mode)
record Env
A stand-in for the ENV capability. A name simply left out of vars reads as unset, which is the whole reason Mock.ENV.unset exists: absence is an answer programs act on. onGet answers instead of the map when a test wants a rule rather than a fixture.
with ENV = Mock.Env { vars: {"HOME": "/fake"} } do
assert(configHome() == "/fake/.config")
end
Fields
Implements ENV.
Functions
lookup
lookup(key)
Named apart from get: this.get(key) would bind to the capability's own get, not to this function.
get
get(key, default)
has?
has?(key)
keys
keys
values
values
count
count
each
each(f)
entries
entries
set
set(name, value)
A write goes to onSet or nowhere. It must NOT reach the real environment: a substituted ENV is the whole point of the mock, and a test that set a variable would otherwise leak it into the next one and into every process the suite starts. vars cannot take it either: a record is immutable, so a test that cares about writes supplies the hook, and one that does not gets a write that goes quietly nowhere.
unset
unset(name)
record Clock
A stand-in for the Time.Clock capability. Every reading in the region (Time.now, Date.today, DateTime.utcNow) answers from it.
with Time.Clock = Mock.Clock { at: DateTime.parse("2026-07-30T14:03:00Z").try } do
assert(Date.today().iso == "2026-07-30")
end
The clock is frozen: every reading is at, zone included, so Date.today() is the date in the zone you wrote and not in the zone of the machine running the test.
onNow answers instead when a test wants a rule rather than a fixture. A block keeps the clock of the region it was written in, so a rule written outside the with reads the real one. That is how to get a clock that still runs, which code measuring elapsed time needs: a frozen clock makes every interval zero.
with Time.Clock = Mock.Clock { onNow: Just({ DateTime.now() + 3.days }) } do
let started = DateTime.epochNanos()
runTheThing()
assert(DateTime.epochNanos() > started)
end
Fields
Implements Time.Clock.
Functions
now
now
module Mock.FS
A stateful stand-in for the filesystem: files a test declares, that the real FS.File then reads back.
The store is global and lives until clear, which is what makes a write followed by a read testable, and what makes two tests able to interfere. Clear it in an after hook.
describe "the config loader" do
before do
Mock.FS.files({ "app.conf": "port = 8080\n" })
end
after do
Mock.FS.clear()
end
it "reads the port" do
Assert.equal(loadPort(), 8080)
end
end
File
File(path: FS.FilePath, content: String) -> Void
Declares one file and its content.
Parameters
path- the path the file appears at
content- its contents
Examples
Mock.FS.File("app.conf", "port = 8080\n")
Directory
Directory(path: FS.FilePath) -> Void
Declares a directory at path.
Parameters
path- the directory to declare
Examples
Mock.FS.Directory("src")
clear
clear : Void
Empties the store, so nothing declared so far is visible any more.
Call it in an after hook: the store is global, and what one test leaves behind the next one sees.
Examples
after do
Mock.FS.clear()
end
files
files(entries: Map<FS.FilePath, String>) -> Void
Declares the whole fixture in one call.
The same shape Mock.Files { files: ... } takes: one line instead of one per file.
Parameters
entries- paths to their contents
Examples
Mock.FS.files({
"app.conf": "port = 8080\n",
"hosts": "localhost\n"
})
onRead
onRead(reader: Reader) -> Void
Answers reads by RULE rather than from a fixture.
Content derived from the path, a failure on the third call, a record of what was asked for. Consulted before the map, and returning None means "no such file", so absence is expressible too.
Parameters
reader- the rule to answer reads with
Examples
Every .txt path has content, nothing else exists
Mock.FS.onRead do |path|
path.endsWith?(".txt") then Just("stub") else None
end
module Mock.ENV
Overlays the process environment, so a test can say what ENV holds instead of depending on how it was launched.
The overlay is global and lives until clear: clear it in an after hook.
Mock.ENV.vars({ "HOME": "/fake", "LOG_LEVEL": "debug" })
assert(configPath() == "/fake/.config")
Mock.ENV.clear()
set
set(name: String, value: String) -> Void
Sets one variable in the overlay.
Parameters
name- the variable name
value- its value
Examples
Mock.ENV.set("LOG_LEVEL", "debug")
unset
unset(name: String) -> Void
Removes one variable from the overlay, so it reads as unset.
Separate from set because a variable being ABSENT is an answer programs act on, and there is no value that means it.
Parameters
name- the variable to remove
Examples
Testing the unset path
Mock.ENV.unset("HOME")
Assert.equal(configPath(), ".")
clear
clear : Void
Removes the whole overlay, restoring the real environment.
Examples
after do
Mock.ENV.clear()
end
vars
vars(entries: Map<String, String>) -> Void
Declares the whole overlay in one call.
Supply a map of variable names to their values. Use with ENV = Mock.Env { onGet: ... } when a callback should decide the value for each lookup instead.
Parameters
entries- variable names to their values
Examples
Mock.ENV.vars({ "HOME": "/fake", "LOG_LEVEL": "debug" })
module Mock.IO
A stateful stand-in for the console: captures what a program prints, and feeds it lines as if they had been typed.
The way to test a program that talks to a person without one being there.
Mock.IO.start()
Mock.IO.input("Ada", "42")
greet()
Assert.equal(Mock.IO.output(), "hello, Ada\n")
Mock.IO.stop()
start
start : Void
Starts capturing output and serving queued input.
Examples
before do
Mock.IO.start()
end
stop
stop : Void
Stops capturing, and restores the real console.
Examples
after do
Mock.IO.stop()
end
output
output : String
Everything the program has printed since capturing started.
Newlines are included, so a single IO.printLine("hi") gives "hi\n".
Returns the captured output
Examples
Mock.IO.start()
IO.printLine("hello")
Assert.equal(Mock.IO.output(), "hello\n")
clear
clear : Void
Discards the captured output, while continuing to capture.
Useful between phases of one test, when only the later output matters.
Examples
setUpNoisily()
Mock.IO.clear()
theThingUnderTest()
Assert.equal(Mock.IO.output(), "done\n")
input
input(first, second, third, fourth) -> Void
Queues the lines IO.getLine will return, in order.
Takes a list, or up to four lines as separate arguments. Once they run out, IO.getLine answers None: end of input, exactly as a closed stdin would.
Parameters
lines- the lines to serve, in order
Examples
A list of lines
Mock.IO.input(["Ada", "42"])
The same, as separate arguments
Mock.IO.input("Ada", "42")
module Mock.Net
Scriptable networking values for testing HTTP, DNS, sockets and WebSockets.
module Mock.Net.HTTP
Canned HTTP transport state for networking specifications.
record Transport
Responses returned in order by a scripted transport.
Fields
responses[Any]
module Mock.Net.DNS
Canned DNS resolver state for networking specifications.
record ResolverScript
Hostname-to-address answers supplied without touching the network.
Fields
module Mock.Net.Socket
Canned byte-stream state for socket specifications.
record Script
Binary chunks delivered in order as incoming socket data.
Fields
incoming[Binary]
module Mock.Net.WebSocket
Canned message state for WebSocket specifications.
record Script
High-level messages delivered in order by a scripted connection.
Fields
incoming[Any]