Skip to main content

Testing

local Test = Quark.Import("Testing")
local Expect = Test.Expect

Test.Describe("Inventory", function()
Test.It("stacks items", function()
local inventory = Inventory.New()
inventory:Add("wood", 3)
inventory:Add("wood", 2)
Expect(inventory:Count("wood")):ToBe(5)
end)

Test.It("saves to the database", function()
local saved = Quark.Await(inventory_repository.Save, inventory)
Expect(saved).Not:ToBeNil()
end)
end)

Test.Run()

Every test runs in its own Quark.Async coroutine, so Quark.Await works directly inside tests and hooks. Tests are registered per package, several packages can run their own tests at the same time.

Defining tests​

Function
Test.Describe(name, body)group tests, groups can be nested
Test.It(name, body, options?) (alias Test.Test)define a test, option Timeout in milliseconds (default 5000)
Test.Only(name, body, options?) / Test.DescribeOnly(name, body)run only these tests while any Only exists
Test.Skip(name, body?) / Test.DescribeSkip(name, body)skip and report as skipped
Test.BeforeAll(fn), Test.AfterAll(fn)run once around the tests of the current group
Test.BeforeEach(fn), Test.AfterEach(fn)run around every test of the current group and its subgroups
Test.Fail(message?)fail the current test

Running​

Test.Run(options?) runs all tests registered by the calling package and returns a promise resolving with a summary { Passed, Failed, Skipped, Duration, Failures = { { Name, Message } } }. Options:

Option
Filteronly run tests whose full name (Group > Subgroup > Test) contains this text
Timeoutdefault timeout for tests and hooks in milliseconds
Silentonly log failures and the summary

Results are logged with Console.Log, failures with Console.Error:

[Testing] Inventory
[Testing] PASS stacks items (0.04 ms)
[Testing] FAIL saves to the database (2.10 ms): [inventory/Server/Tests.lua]:12: expected nil not to be nil
[Testing] 1 passed, 1 failed, 0 skipped (2.31 ms)

Expectations​

Expect(value) returns an expectation. Expect(value).Not negates every matcher.

MatcherPasses when
:ToBe(expected)value == expected
:ToEqual(expected)the values are deeply equal, tables compared key by key
:ToBeNil(), :ToBeTruthy(), :ToBeFalsy()as named
:ToBeType(name)type(value) matches, "integer" and "float" check math.type
:ToBeGreaterThan(n), :ToBeGreaterThanOrEqual(n), :ToBeLessThan(n), :ToBeLessThanOrEqual(n)numeric comparison
:ToBeCloseTo(expected, tolerance?)the difference is at most tolerance (default 1e-9)
:ToContain(item)a string contains the text, or a table contains a deeply equal value
:ToMatch(pattern)the string matches the Lua pattern
:ToHaveLength(n)#value == n for strings, tables and buffers
:ToThrow(fragment?)the function raises an error, containing fragment if given
:ToHaveBeenCalled(), :ToHaveBeenCalledTimes(n), :ToHaveBeenCalledWith(...)spy calls

Test.Equal(a, b) exposes the deep comparison, Test.Show(value) the formatting used in failure messages.

Spies and mocks​

Function
Test.Spy(implementation?)a function recording its calls, optionally forwarding to implementation
Test.Mock(table, key, implementation?)replaces table[key] with a spy, restored after the current test
Test.GetCalls(spy)list of calls, every call is a packed argument list
Test.ResetSpy(spy)forget recorded calls
Test.SetImplementation(spy, implementation)change what the spy does

Spies are plain functions, so they can be passed anywhere a callback is expected.

Test.It("logs the greeting", function()
local log = Test.Mock(Console, "Log")
Greet("Alice")
Expect(log):ToHaveBeenCalledWith("Hello Alice")
end)

Running tests without a server​

tools/test-host/run.sh [package] runs a package outside of nanos world on Linux. It builds Quark, compiles a small host that embeds the SDK's Lua, emulates the parts of the server API Quark relies on (Server.Subscribe("Tick"), Package, Console, one environment per package) and ticks every 16 ms. The package signals completion through the host only global QuarkTestHost:

Test.Run():Then(function(summary)
if QuarkTestHost then
QuarkTestHost.Finish(summary.Failed)
end
end)

The exit code is 0 when all tests passed, which makes it suitable for CI. The default package is packages/quark-test, the test suite of Quark itself. Everything else of the nanos world API is missing in the host, so tests of gameplay code still need the real server.