kex docs Standard Library

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. Mock operations are allowed in spec files, the REPL, and programs run with --allow-mocks.

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 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

files
Map<FS.FilePath, String> optional
onRead
Reader? 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)

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

vars
Map<String, String> optional
onGet
Lookup? optional
onSet
Writer? optional

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)

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

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]