Pre-release Harn is pre-1.0 — the language, standard library, and CLI may change between releases. See the release notes

Test framework

Harn includes a built-in test runner invoked via harn test.

Running tests

harn test path/to/tests/         # run all test files in a directory
harn test path/to/test_file.harn # run tests in a single file

Test discovery

The test runner scans .harn files for pipelines whose names start with test_. Each such pipeline is executed independently. A test passes if it completes without error; it fails if it throws or an assertion fails.

pipeline test_addition() {
  assert_eq(1 + 1, 2)
}

pipeline test_string_concat() {
  const result = "hello" + " " + "world"
  assert_eq(result, "hello world")
}

Pipelines can also opt in with @test, including table-driven cases. A @test(cases: [...]) attribute creates one independent test case per row. Each row must be a dict with a unique string name and an args list whose length matches the pipeline parameter count. Reports and filters use the display name pipeline[row].

@test(cases: [
  {name: "positive", args: [2, 3, 5]},
  {name: "negative", args: [-2, 1, -1]},
])
pipeline add_case(left, right, expected) {
  assert_eq(left + right, expected)
}

Assertions

Three assertion builtins are available. They can be called anywhere, but they are intended for test pipelines and the linter warns on non-test use:

FunctionDescription
assert(condition, message?)Throws if condition is falsy
assert_eq(a, b, message?)Throws if a != b, showing both values
assert_ne(a, b, message?)Throws if a == b, showing both values

All three accept an optional custom message. If message is omitted, nil, an empty string, or the literal string "null" (the common result of json_stringify-ing a value that turned out to be nil), the assertion falls back to its default message instead of throwing that uninformative value verbatim.

Captured output

log, print, println, and related output builtins write into a per-case buffer rather than directly to the terminal. A passing test stays quiet by default; a failing test always prints its buffered output alongside the failure. Pass --verbose to see a passing test's captured output too. --json-out and --junit reports include it under captured_output (JUnit: <system-out>) whenever it is non-empty.

Mock LLM provider

During harn test, the HARN_LLM_PROVIDER environment variable is automatically set to "mock" unless explicitly overridden. The mock provider returns deterministic placeholder responses, allowing tests that call llm, llm_stream, or llm_stream_call to run without API keys.

CLI options

FlagDescription
--filter <pattern>Only run tests whose names contain <pattern>
--verbose / -vShow per-test timing and detailed failures
--timingShow per-test timing and summary statistics
--timeout <ms>Per-test timeout in milliseconds (default 30000)
--parallelRun test files concurrently
--fail-fastStop scheduling new tests after the first failure; already-running parallel tests finish
--junit <path>Write JUnit XML report to <path>
--recordRecord LLM responses to .harn-fixtures/
--replayReplay LLM responses from .harn-fixtures/