Specium
A lightweight and flexible testing framework for Roblox.
Specium allows you to organize tests into suites and sub-suites, run them and get a detailed report of the results.
Types
SpeciumResult
interface SpeciumResult {success: boolean--
Whether the test passed or failed
message: string--
A message describing the result
}SpeciumRunResult
interface SpeciumRunResult {total: number--
Total number of tests run
passed: number--
Number of tests that passed
failed: number--
Number of tests that failed
suites: {SpeciumSuiteResult}--
Results for each root suite
}Functions
suite
Specium.suite(name: string,--
The name of the suite
) → SpeciumSuite--
The constructed suite, ready to be passed to Specium.run
Types
interface SpeciumSuiteContext {it: (name: string,timeout: number?) → ()--
Registers a test case inside the current suite
skip: (name: string,timeout: number?) → ()--
Registers a test case inside the current suite
beforeAll: (() → ())?--
Runs once before all tests in the suite
afterAll: (() → ())?--
Runs once after all tests in the suite
beforeEach: (() → ())?--
Runs before each test in the suite
afterEach: (() → ())?--
Runs after each test in the suite
}Creates a new test suite.
Example:
local suite = Specium.suite("Math", function(t)
t.it("adds numbers", function()
Specium.expect(1 + 1).toBe(2)
return Specium.success("ok")
end)
t.describe("subtraction", function(t)
t.it("subtracts numbers", function()
Specium.expect(5 - 3).toBe(2)
return Specium.success("ok")
end)
end)
end)
run
Specium.run(suite: SpeciumSuite--
The suite to run
) → (string,--
A formatted output string with a tree-view of results and a summary
SpeciumRunResult--
A structured table with the full results
)Runs all tests in a suite (including sub-suites).
Example:
local output, results = Specium.run(suite)
print(output)
-- > Math
-- [Pass] adds numbers - ok
-- > subtraction
-- [Pass] subtracts numbers - ok
--
-- Result:
-- 2/2 (100%) tests passed
runTests
Specium.runTests() → (string,--
A concatenated output string of all suite results
)Scans one or more folders for .spec ModuleScripts and runs each one as a suite.
Each .spec module must return a SpeciumSuite created via Specium.suite.
If a module fails to load, a warning is emitted and it is skipped.
Example:
-- Single folder
local output, results = Specium.runTests(ServerScriptService.Tests)
print(output)
-- Multiple folders
local output, results = Specium.runTests({
ServerScriptService.Tests,
ReplicatedStorage.Shared.Tests,
})
print(output)
error
Specium.error(message: string--
The failure message
) → SpeciumResult--
A result with success = false
Returns a failed SpeciumResult with the given message.
Example:
t.it("always fails", function()
return Specium.error("this test is not implemented yet")
end)
success
Specium.success(message: string--
The success message
) → SpeciumResult--
A result with success = true
Returns a successful SpeciumResult with the given message.
Example:
-- Returning Specium.success is optional — if omitted, the test is marked as passed automatically.
t.it("works", function()
Specium.expect(1 + 1).toBe(2)
return Specium.success("all good")
end)
expect
Specium.expect(received: any--
The value to assert against
) → SpeciumMatchers--
A table of matcher functions
Creates a matcher chain for the given value.
Use .never to invert any matcher, and .withMessage(msg) to prepend
a custom message to any failure.
Available matchers:
toBe(expected)— strict equality (==)toEqual(expected)— deep equality (tables compared recursively)toBeA(type, legacy?)— checkstypeof(ortypeiflegacyis true)toBeTruthy()— value is notniland notfalsetoBeNil()— value isniltoBeNear(expected, epsilon?)— numeric proximity (default epsilon:1e-5)toBeGreaterThan(expected)— numeric>toBeLessThan(expected)— numeric<toContain(item)— table contains value (deep) or string contains substringtoHaveLength(expected)— checks#valuetoMatch(pattern)— string matches a Lua patterntoThrow(expected?, ...args)— function throws when called withargswithMessage(msg)— prepends a custom message to any failurenever— inverts the next matcher
Example:
-- All examples below pass
Specium.expect(2 + 2).toBe(4)
Specium.expect("hello world").toContain("world")
Specium.expect({1, 2, 3}).toHaveLength(3)
Specium.expect(math.huge).never.toBeNil()
Specium.expect(function() error("oops") end).toThrow()
Specium.expect(42).withMessage("answer should be 42").toBe(42)
Specium.expect(1 + 1).never.toBe(3)