Skip to main content

Unit Testing

Rank ships with a lightweight, deterministic test runner through rank test. Use it when you want to pin either the evaluated output or the expected diagnostics for a program without building a separate host harness.

What rank test is for

Use rank test when you need:

  • repeatable output checks for pure programs
  • deterministic fixtures for Env<T> {} and HTTP::Fetch<T> {}
  • local or stubbed provider behavior instead of live backends
  • regression coverage for diagnostics as well as success output
  • machine-readable suite reports for CI

The command accepts either one case directory or a suite root that contains many case directories:

rank test examples/tests
rank test examples/tests/yaml-expected
rank test examples/tests --filter provider
rank test examples/tests --format json

Test case layout

Each case directory is driven by a rank-test.json manifest.

Minimal value-output case:

my-case/
rank-test.json
expected.json
src/
main.rank

Minimal manifest:

{
"kind": "rank/test-case",
"version": 1,
"entry": "src/main.rank",
"expectedValue": "expected.json"
}

expectedValue may point to JSON or YAML. Use JSON when you want the least ambiguity in scalar values and YAML when you want the fixture to read like normal Rank output.

Output assertions

For ordinary values, the expected file contains the realized document value.

Example expected JSON:

{
"service": {
"host": "localhost",
"port": 443
}
}

If pub main returns Emit::manifest(...), the expected value should be the descriptor envelope, not the materialized filesystem tree:

{
"kind": "rank/output-manifest",
"version": 1,
"entries": [
{
"path": "README.md",
"format": "text",
"value": "# api\n"
}
]
}

Diagnostic assertions

Tests can also pin expected diagnostics instead of an output value.

Example manifest:

{
"kind": "rank/test-case",
"version": 1,
"entry": "src/main.rank",
"expectedDiagnostics": [
{
"code": "TYP007",
"messageContains": "Field missing does not exist"
}
]
}

Use exact message only when the full text is intentionally stable. Prefer messageContains for diagnostics that include long rendered type shapes or paths.

Deterministic external inputs

Case directories may include fixture files for external inputs:

  • env.json for Env<T> {} reads
  • http.json for HTTP::Fetch<T> {} snapshots
  • provider-stubs.json for backend provider outputs keyed by public export name and exact full input
  • a local rank.toml plus providers/ directory when you want to exercise a path-based provider package directly

This keeps the suite reproducible even when the production program normally depends on host state or remote services.

Backend provider stubs

provider-stubs.json is a small object keyed by public provider export name:

{
"api::users::lookup": [
{
"input": {
"id": "123",
"cacheKey": "lookup-123"
},
"output": {
"name": "stubbed-user-123"
}
}
]
}

Each entry matches the backend provider call by exact full input. If a backend invocation has no matching stub, rank test fails instead of falling through to a live runtime call.

Running suites in CI

Text output is good for local iteration:

rank test examples/tests

Structured reports are better for automation:

rank test examples/tests --format json
rank test examples/tests --format yaml

--filter <pattern> is useful when narrowing one failing slice inside a larger suite.

Where to look next