Basic Usage
Specium works by organizing tests into suites. Each suite contains test cases registered with t.it, and optionally nested sub-suites with t.describe.
Creating a Suite
local Specium = require(path.to.Specium)
local suite = Specium.suite("Math", function(t)
t.it("adds numbers", function()
Specium.expect(1 + 1).toBe(2)
end)
end)
Running a Suite
local output, results = Specium.run(suite)
print(output)
-- > Math
-- [Pass] adds numbers
--
-- Result:
-- 1/1 (100%) tests passed
Nested Suites
Use t.describe to create sub-suites:
local suite = Specium.suite("Math", function(t)
t.it("adds numbers", function()
Specium.expect(1 + 1).toBe(2)
end)
t.describe("subtraction", function(t)
t.it("subtracts numbers", function()
Specium.expect(5 - 3).toBe(2)
end)
end)
end)
Returning a Result
Returning a value from a test is optional. If nothing is returned, the test is marked as passed automatically.
You can use Specium.success and Specium.error to return explicit results with a message:
t.it("works", function()
return Specium.success("all good")
end)
t.it("not implemented", function()
return Specium.error("todo")
end)
Auto-discovery
Place your test files anywhere inside a folder and name them with the .spec suffix (e.g. MathUtils.spec). Each module should return a SpeciumSuite:
-- MathUtils.spec.luau
return Specium.suite("MathUtils", function(t)
t.it("works", function()
Specium.expect(1 + 1).toBe(2)
end)
end)
Then run them all at once:
local output, results = Specium.runTests(ServerScriptService.Tests)
print(output)
Skipping Tests
Use t.skip to mark a test as skipped. The test will appear in the output but its function will never run.
local suite = Specium.suite("Math", function(t)
t.it("adds numbers", function()
Specium.expect(1 + 1).toBe(2)
end)
t.skip("not implemented yet", function()
Specium.expect(1 + 1).toBe(3)
end)
end)
-- > Math
-- [Pass] adds numbers
-- [Skip] not implemented yet
--
-- Result:
-- 1/1 (100%) tests passed, 1 skipped
Lifecycle Hooks
Use hooks to run setup and cleanup logic around your tests.
local suite = Specium.suite("Database", function(t)
local db
t.beforeAll(function()
db = Database.connect()
end)
t.afterAll(function()
db:disconnect()
end)
t.beforeEach(function()
db:reset()
end)
t.it("inserts a record", function()
db:insert({ id = 1 })
Specium.expect(db:count()).toBe(1)
end)
t.it("deletes a record", function()
db:insert({ id = 1 })
db:delete(1)
Specium.expect(db:count()).toBe(0)
end)
end)
beforeAll— runs once before all tests in the suiteafterAll— runs once after all tests in the suitebeforeEach— runs before each testafterEach— runs after each test
Hooks are scoped to their suite and do not affect parent or sibling suites.
Timeouts
Pass a timeout (in seconds) as the third argument to t.it or t.skip. If the test takes longer than the limit, it fails automatically with a timeout message.
local suite = Specium.suite("Network", function(t)
t.it("fetches data", function()
local result = HttpService:GetAsync("https://example.com")
Specium.expect(result).never.toBeNil()
end, 5)
end)
-- > Network
-- [Failure] fetches data - Timeout after 5s
--
-- Result:
-- 0/1 (0%) tests passed
Tests without a timeout run indefinitely.