Mock
type Reader
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.
When a test only needs canned ANSWERS, replacing the capability is the better tool: with FS.File = MyFake { ... } do ... end swaps the implementation for one lexical region, holds no global state, needs no clearing, and cannot leak into another test. See spec/capability_stdlib_fs.kex for the shape of a stand-in (kexhq/kex#143).
Opt-in on purpose (issue #144): this module used to ride along inside a prelude networking file, so importing the prelude made every Mock.* reachable from every program without anyone asking for it. Reachable is still not callable: the runtime denies the mock intrinsics outside spec files, the REPL, and --allow-mocks, but it should also not be in scope by accident. A qualified Mock.FS.File(...) auto-loads this file like any other opt-in module.
All Mock.* sub-modules live in a single module Mock block so merged compilation units never see duplicate top-level Mock modules. A stand-in's read hook: a path in, its content or None out.
Variants
- abstract
type Lookup
An environment stand-in's lookup hook: a name in, its value or None out.
Variants
- abstract
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.
Variants
- abstract
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 still wants Mock.FS (kexhq/kex#143).
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
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
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
function File
Declares one file and its content.
File(path, content) : FS.FilePath -> String -> Void
Parameters
pathFS.FilePath- the path the file appears at
contentString- its contents
Returns: Void —
Examples
Mock.FS.File("app.conf", "port = 8080\n")
function Directory
Declares a directory at path.
Directory(path) : FS.FilePath -> Void
Parameters
pathFS.FilePath- the directory to declare
Returns: Void —
Examples
Mock.FS.Directory("src")
function clear
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.
clear() : Void
Returns: Void —
Examples
after do
Mock.FS.clear()
end
function files
Declares the whole fixture in one call.
The same shape Mock.Files { files: ... } takes: one line instead of one per file (kexhq/kex#143).
files(entries) : Map<FS.FilePath, String> -> Void
Parameters
entriesMap<FS.FilePath, String>- paths to their contents
Returns: Void —
Examples
Mock.FS.files({
"app.conf": "port = 8080\n",
"hosts": "localhost\n"
})
function onRead
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.
onRead(reader) : Reader -> Void
Parameters
readerReader- the rule to answer reads with
Returns: Void —
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()
function set
Sets one variable in the overlay.
set(name, value) : String -> String -> Void
Parameters
nameString- the variable name
valueString- its value
Returns: Void —
Examples
Mock.ENV.set("LOG_LEVEL", "debug")
function unset
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.
unset(name) : String -> Void
Parameters
nameString- the variable to remove
Returns: Void —
Examples
Testing the unset path
Mock.ENV.unset("HOME")
Assert.equal(configPath(), ".")
function clear
Removes the whole overlay, restoring the real environment.
clear() : Void
Returns: Void —
Examples
after do
Mock.ENV.clear()
end
function vars
Declares the whole overlay in one call.
The same shape Mock.Env { vars: ... } takes (kexhq/kex#143). There is no onGet here: global ENV is a materialised Map, so there is nothing for a callback to intercept: use with ENV = Mock.Env { onGet: ... } when a rule is what you want.
vars(entries) : Map<String, String> -> Void
Parameters
entriesMap<String, String>- variable names to their values
Returns: Void —
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()
function start
Starts capturing output and serving queued input.
start()
Returns: Void —
Examples
before do
Mock.IO.start()
end
function stop
Stops capturing, and restores the real console.
stop()
Returns: Void —
Examples
after do
Mock.IO.stop()
end
function output
Everything the program has printed since capturing started.
Newlines are included, so a single IO.printLine("hi") gives "hi\n".
output()
Returns: String — the captured output
Examples
Mock.IO.start()
IO.printLine("hello")
Assert.equal(Mock.IO.output(), "hello\n")
function clear
Discards the captured output, while continuing to capture.
Useful between phases of one test, when only the later output matters.
clear()
Returns: Void —
Examples
setUpNoisily()
Mock.IO.clear()
theThingUnderTest()
Assert.equal(Mock.IO.output(), "done\n")
function input
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.
input(lines)
Parameters
lines[String]- the lines to serve, in order
Returns: Void —
Examples
A list of lines
Mock.IO.input(["Ada", "42"])
The same, as separate arguments
Mock.IO.input("Ada", "42")
make Files implements FS.File
cannedRead
Named apart from read: this.read(path) would bind to the capability's own read : FilePath -> String?, not to this method.
cannedRead(path)
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)
open
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.
open(path, mode)
write
write(path, content)
writeBytes
writeBytes(path, content)
append
append(path, content)
delete
delete(path)
copy
copy(src, dst)
rename
rename(src, dst)
make Env implements ENV
lookup
Named apart from get: this.get(key) would bind to the capability's own get, not to this method.
lookup(key)
get
get(key)
has?
has?(key)
keys
keys()
values
values()
count
count()
each
each(f)
entries
entries()
set
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.
set(name, value)
unset
unset(name)
module Mock.Net
Scriptable networking values are namespaced so importing Mock does not recreate any of the removed global HTTP types.
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
answers{String: [String]}
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]