Test.Runner.UnitNode
An xUnit / Python-unittest-style test framework for Gren.
This test runner has:
- Suite- and test-level fixtures with
setUpSuite/tearDownSuite(once per group) andsetUp/tearDown(once per test) — see suite. - Lifecycle failures are first-class: a thrown
setUp/tearDownbecomes a recorded error, never a silent skip or a crash. - CLI selection by name or glob, verbose per-test timing, and JUnit XML output — all in run.
A single test, parameterized by the test-fixture type its body receives.
Build one with test. Within a suite call, every test must share the
same fixture type (it's whatever that suite's setUp produces).
A test's result: passed, failed an assertion, or errored (threw). Re-exported for reporters/consumers; you rarely match on it directly.
Define a test group with the full xUnit lifecycle.
setUpSuite runs once and produces a suite fixture handed to every setUp;
setUp runs before each test and produces the test fixture handed to the
body and to tearDown. The two fixture type variables are erased here, so the
returned Suite is monomorphic and groups with unrelated fixtures share one
Array Suite.
Define a single test: a name and a body that, given the suite's test
fixture, runs effects and returns an Expectation.
test "is not 1970" <| \_ ->
Time.now |> Task.map (\t -> Expect.notEqual 0 (Time.posixToMillis t))
A setUpSuite/setUp that produces the empty fixture {} — for groups or
tests that need no fixture value.
A setUp or globalSetUp that ignores its fixture and produces {}.
A tearDown, tearDownSuite, or globalTearDown that does nothing.
Build a test executable with no permissions. Use this when your tests are purely computational. For tests that need filesystem, network, or process access, use runWith.
main : Node.SimpleProgram a
main =
Test.Runner.UnitNode.run
{ name = "my-tests"
, version = "1.0.0"
, suites = [ arithmetic, stringTests ]
}
Like run, but you supply the initialization step. Use this when
your tests need permissions other than fs + childProcess, or none at all.
See Test.Runner.UnitNode.Program.runWith for full documentation.
Writing a suite
import Test.Runner.UnitNode as U
import Expect
arithmetic : U.Suite
arithmetic =
U.suite
{ name = "Arithmetic"
, setUpSuite = U.noSuiteFixture
, tearDownSuite = U.noTearDown
, setUp = U.noFixture
, tearDown = U.noTearDown
, tests =
[ U.test "adds" <| \_ ->
Task.succeed (Expect.equal 4 (2 + 2))
]
}
A fixture-bearing suite threads a value from setUp into each test body:
U.suite
{ name = "TempDir"
, setUpSuite = U.noSuiteFixture
, tearDownSuite = U.noTearDown
, setUp = \_ -> FileSystem.makeTempDirectory perms.fs "ut" |> mapErr
, tearDown = \dir -> FileSystem.remove perms.fs { recursive = True } dir |> mapErr
, tests =
[ U.test "writes a file" <| \dir ->
writeAndCheck dir -- : Task String Expectation
]
}
Every lifecycle task and every test body works in Task String _: the success
channel carries the value (or the test's Expectation), and the error
channel is a String you render yourself. Keeping errors as String (rather
than an arbitrary type) is what lets the runner print them verbatim instead of
mangling them through Debug.toString.