Testing#
Lua’s standard library ships assert and no test runner. The
operator picks a framework from LuaRocks; the de-facto standard
in 2026 is busted,
which gives the operator BDD-style describe / it blocks,
spies, mocks, and parallel execution. luaunit is the
xUnit-flavoured alternative.
/errors`. For the build / lint side of the same toolchain, see Tooling.
busted#
Install with LuaRocks.
$ luarocks install busted
$ busted --version
Specs live under spec/ by default and end in _spec.lua.
busted discovers them and runs all matching files.
-- spec/string_spec.lua
describe("string helpers", function()
it("trims trailing whitespace", function()
assert.are.equal("hi", trim("hi "))
end)
it("uppercases", function()
assert.are.equal("HI", string.upper("hi"))
end)
describe("when given nil", function()
it("raises", function()
assert.has_error(function() trim(nil) end)
end)
end)
end)
$ busted spec/
●●●
3 successes / 0 failures / 0 errors / 0 pending : 0.005 seconds
Setup and teardown#
before_each and after_each run around every it;
setup and teardown run once per describe.
describe("Server", function()
local server
before_each(function()
server = Server.new(8080)
server:start()
end)
after_each(function()
server:stop()
end)
it("answers ping", function()
assert.are.equal("pong", server:ping())
end)
end)
Assertions#
busted ships its own assert (replacing the global) with
a fluent API.
Assertion |
Meaning |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
deep structural equality |
|
|
|
|
Mocks and spies#
spy wraps a function and records every call; mock wraps
an entire table.
it("calls the logger on failure", function()
local log = mock({warn = function() end})
run_with_failure(log)
assert.spy(log.warn).was_called_with("boom")
end)
it("tracks call count", function()
local s = spy.new(function() end)
s(); s(); s()
assert.spy(s).was.called(3)
end)
Coverage#
luacov instruments
the runtime and writes luacov.stats.out per run; the
luacov command renders it.
$ luarocks install luacov
$ busted --coverage spec/
$ luacov
$ cat luacov.report.out
luaunit#
For projects already on luaunit (xUnit style, no DSL), the
operator writes test classes as tables whose methods start with
test.
luaunit = require("luaunit")
TestString = {}
function TestString:setUp() self.s = " hi " end
function TestString:testTrim()
luaunit.assertEquals(trim(self.s), "hi")
end
os.exit(luaunit.LuaUnit.run())
Property-based#
lua-quickcheck adds property-based generation for the operator who wants to explore the input space rather than enumerate it.
local qc = require("quickcheck")
qc.property("reverse twice is identity")
:withtype("string")
:check(function(s) return rev(rev(s)) == s end)