Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Fixtures And Assertions

Fixtures are the interfaces available to a test callback. They keep tests focused on observable behavior instead of backend implementation details.

Request only the fixtures a test uses:

test."service is healthy" = { machine }: [
  (machine.configure { modules = [ serviceModule ]; })
  (machine.service "example.service").start
  (expect (machine.service "example.service")).toBeActive
  ((expect (machine.http.get "http://localhost/health")).toHaveStatus 200)
];

Core Fixtures

FixtureUse it to
terminalOpen and interact with a local terminal application
machineConfigure and control one NixOS VM
machinesConfigure and control named NixOS VMs
filesystemPrepare mutable files and observe machine paths
serviceStart, stop, restart, and reload services
networkLocate endpoints and partition named machines
httpObserve idempotent requests or send one request once
userLocate users and run commands as a user
containerLocate and control declarative NixOS containers
browserInteract through accessibility-oriented browser locators
desktopLocate windows and text and send desktop input
resultInspect saved command and HTTP results

expect is a module argument, not a test fixture. Import it at module scope and use it with targets returned by fixtures.

Locators

A locator describes what to observe without performing the assertion itself:

terminal.getByText "ready"
machine.service "example.service"
machine.file "/run/example/ready"
machine.http.get "http://localhost/health"

Prefer semantic locators over shell commands when both express the same public behavior. They produce clearer tests and diagnostics.

Matchers

Matchers live under expect and accept compatible locators or targets:

(expect (terminal.getByText "ready")).toBeVisible
(expect (machine.service "example.service")).toBeActive
(expect (machine.file "/run/example/state")).toHaveContent "ready"
(expect (machine.http.get "http://localhost/health")).toHaveStatus 200

Observable-state matchers retry until they pass or the timeout expires. They do not repeat preceding side effects.

Commands As An Escape Hatch

Use commands when no semantic locator describes the behavior:

(machine.command "example status")
(expect (machine.command "example is-ready")).toEventuallySucceed
(expect (machine.command "example forbidden-operation")).toFail

machine.command as a standalone action runs once. Command matchers retry, so only use them for safe observations.

Saved Results

Use machine.run or http.send when an operation has side effects. Save its result once, then make assertions without repeating the operation:

test."creates an item once" = { machine, result }: [
  (machine.run {
    command = "example create";
    saveAs = "create";
  })
  ((expect (result.command "create")).toHaveExitCode 0)
  ((expect (result.stdout "create")).toContainStdout "created")
];

See the guides for complete workflows and the API reference for exact signatures.