lloyAlloy on GitHub
alloy test
Docs

alloy test

Updated

A test lives beside the code it tests: a @test function in the module, blanked from the ship artifact. @test on a namespace marks the whole group, so every public function of it is a test named Suite.name. alloy test builds the project, then writes one lest spec per source that holds a @test, under [test] out:

src/inventory.aly        ->  tests/inventory.spec.luau
src/ui/menu.alx          ->  tests/ui/menu.spec.luau
src/loot.spec.aly        ->  tests/loot.spec.luau

A spec carries the tests and every top-level statement they reach: the imports they use, the locals and functions they call, the structs and the impls those need. The rest of the module stays out, so its side effects stay out of the test VM. An import that only tests read leaves the ship artifact with them. An import inside a test requires its module on the first line of the spec, because lest's native backend resolves a require only while the spec loads. The slice keeps the source's lines, so a failure points at the real one. Each relative require in the spec points at the build output, the runtime included, and the spec ends with a describe that registers each test by name; an async test is awaited.

local function clamp01(x: number): number
    return math.clamp(x, 0, 1)
end

@test
function clamp_keeps_range()
    $assert_eq(clamp01(2), 1)
end

$expect(value) gives the runner's expectation object, so $expect(hp):toBe(100) and $expect(open_chest):toThrow() read as they do in jest. It needs @test on the function or the namespace around it, and it needs [test] lest on; alloy doc $expect lists what the matchers take.

alloy test                 build, then write the specs
alloy test --run           and run lest on the suite
alloy test --coverage      run lest with line coverage
alloy test --filter hit    run the tests whose name holds `hit`
alloy test -- --reporter json   the rest goes to lest as given
alloy test --watch         write again after every change
alloy test --check         write nothing; fail when a spec would change
alloy test src/game.aly    one file's spec, to stdout

The [test] table:

alloy.tomlalloy.toml
[test]
out = "tests"     # where the specs go
suite = "alloy"   # the suite name in lest.toml
lest = true       # write lest.toml and the @lest alias when the root has none

With lest = true, the first run writes a lest.toml with one suite over tests/**/*.spec.luau on the native backend, and adds lest = ".lest/core" to the aliases of the project's Luau configuration, the .config.luau or the .luaurc. That folder is where lest puts its framework. lest then runs the suite; on its VM the runtime falls back to plain coroutines. With shim = true, a task.wait parks its thread, and the spec steps the parked threads until an async test's Future settles, so an await in the test returns. A test whose Future no parked thread settles fails with that message. $assert, $assert_eq, and a matcher that fails raise, and lest reports the line.