Skip to content
jess
api

@jess/test

Test suites in Luau: describe, it, hooks, expectations with deep equality, error assertions, log capture, and a report.

require('@jess/test'): { new, expect }

A built-in module, typed as Module__jess_test. A test module builds a suite, registers cases on it, and returns it; jess test runs the suite and prints the report, and a script can call suite.run() itself. Every case runs inside a log.capture, so what it logged ends up in its result instead of on stderr.

--!strict
local test = require '@jess/test'
local expect = test.expect
local suite = test.new()

local greeter = require '@greeter'

suite.describe('greet', function()
  local names: { string }

  suite.beforeEach(function()
    names = { 'ada', 'grace' }
  end)

  suite.it('greets by name', function()
    expect(greeter.greet(names[1])).toBe 'Hello, ada.'
  end)

  suite.it('greets everyone in order', function()
    expect(greeter.greetAll(names)).toEqual { 'Hello, ada.', 'Hello, grace.' }
  end)

  suite.it('refuses an empty name', function()
    expect(function() greeter.greet '' end).toThrow 'empty'
  end)
end)

return suite

test.new§

test.new() -> JessTestSuite

An empty suite. Its functions are plain functions, not methods: suite.it(...), not suite:it(...).

JessTestSuite.describe§

suite.describe(name: string, body: () -> ()) -> ()

A group. body runs at once and registers cases and hooks; a case's full name is its groups' names and its own, joined with / . Groups nest.

JessTestSuite.it§

suite.it(name: string, body: () -> ()) -> ()

One case. It passes when its beforeEach hooks, its body, and its afterEach hooks all return without an error.

JessTestSuite.beforeEach, afterEach§

suite.beforeEach(body: () -> ()) -> ()

Hooks for every case in the current group and the groups inside it. beforeEach hooks run outermost first; afterEach hooks run innermost first, and they run even when setup or the body failed. Every failure is in the case's error, one traceback each.

JessTestSuite.run§

suite.run() -> JessTestReport

Runs every case in registration order and returns { passed, failed, tests }, each test a { name, passed, error, logs }. Nothing is printed; that's the host's job. Cases can't be added while the suite runs.

test.expect§

test.expect(actual: unknown) -> JessTestExpectation

An assertion on actual. Each check raises with what it saw and what it wanted, tables rendered sorted and shortened: expected { 1, name = "a" } to equal { name = "b" }.

CheckPasses when
toBe(expected)rawequal(actual, expected).
toEqual(expected)Deep equality: same keys, values compared the same way, cycles handled. Tables only compare with tables; anything else is rawequal.
toBeTruthy(), toBeFalsy(), toBeNil()What they say.
toContain(item)actual is a string containing item, or a list with an element that toEquals it.
toBeCloseTo(expected, tolerance?)actual is a number within tolerance (default 1e-9) of expected, for floating-point results that needn't be exact.
toThrow(contains?)actual is a function that raises, and its message contains contains when given. The one place a test catches an error on purpose.

Running§

jess test a.jsl b.jsl loads each module, runs the suite it returns, and prints one report; it exits non-zero when anything failed. From Rust, jess::lua::testing::run does the same and hands back the report.

Types§

type JessTestSuite = {
  describe: (name: string, body: () -> ()) -> (),
  it: (name: string, body: () -> ()) -> (),
  beforeEach: (body: () -> ()) -> (),
  afterEach: (body: () -> ()) -> (),
  run: () -> JessTestReport,
}
type JessTestResult = { name: string, passed: boolean, error: string?, logs: { JessLogRecord } }
type JessTestReport = { passed: number, failed: number, tests: { JessTestResult } }