Writing tests
To start off, this page only covers how Luau Run finds and runs your tests. It is not a general guide to writing good tests.
Which files are tests
Section titled “Which files are tests”luau-run test runs two kinds of file under the folders in roots (tests
unless set):
*.spec.luauis a Jest spec*.test.luauis a script test
No other name counts, not even *.spec.lua. Hidden folders, files a
.gitignore ignores, and folders such as node_modules are skipped.
Which files are found has the
full rules. A path on the command line replaces roots for that run.
Jest specs
Section titled “Jest specs”Jest specs run on Jest-Portable, Luau Run’s version of Jest Roblox that also runs on Lute.
A Jest spec takes Jest from require("@jest/globals"), as the example in
Getting started does. On Lute,
describe, it and expect are also globals.
A spec has task, delay, tick, time and DateTime, which Jest’s fake
timers control. On Lute they also control a spec’s @lute/task and
@std/task, and jest.requireActual still gets the real library. Nothing
else is added: a bare Promise or game is nil on Lute.
On Lute, a spec that requires a project’s own copy of Jest’s globals by another
path, such as @pkg/@jsdotlua/jest-globals, loads that copy instead of
Jest-Portable. Only the Studio lane swaps the copy for Jest-Portable.
Snapshots cannot be written: toMatchSnapshot fails for a snapshot that does
not exist.
Script tests
Section titled “Script tests”A *.test.luau file runs on Lute in its own process, from the project’s
folder, and passes when it finishes without an error. A failure names its file
and line:
FAIL tests/tools/Codec.test.luau:3 Failure: a round trip keeps the third valueWhen a script registers cases with Lute’s @std/test, Luau Run reports each
case, as lute test does. A script test that returns a function fails, since
nothing would call it. To run your own test kit, see
Custom runners.
A script test ends when its file finishes. Luau Run does not wait for tasks the file leaves behind, or report their errors. Waiting for them is on the roadmap.
Luau Run refuses a script test whose full path is 260 characters or more, since Lute on Windows cannot open it. Move the project to a shorter folder, or shorten the path.
Lute is not the luau command-line tool: it has no collectgarbage,
getfenv, setfenv or loadstring.
On Lute, script tests and Jest specs get the environment variables Luau Run
was started with, PATH included. Luau Run overrides a few, such as TEMP and
TMP, and sets UV_THREADPOOL_SIZE to 1.
Choosing a lane and a runner
Section titled “Choosing a lane and a runner”Each file gets a runtime, lute or roblox. The runtime and the file’s runner
decide which lane runs it, such as Jest specs on Lute or Jest specs in Studio.
The runtime is picked in order of priority:
-
a header at the top of the file, before any code:
-- luau-run: runtime=roblox--!strict -
--runtimeon the command line -
the last
[[test.runtime-rules]]entry whose glob matches the file -
the
runtimeof the custom runner a runner rule gives the file -
default-runtimeunder[test]
A [[test.runner-rules]] entry changes the runner for the files its glob
matches: jest, script, or a runner the project declares. Both kinds of rule
look like this:
[[test.runner-rules]]glob = "src/**/*.test.luau"runner = "jest"
[[test.runtime-rules]]glob = "tools/**"runtime = "lute"--log-warnings shows where rules disagree and where a header was ignored.
© 2026 PRMS Developer