Testing Functions
Test DSL with describe/it blocks, assertions, and factory functions.
Test DSL
describe(description, def)
Define a test group. context is an alias.
test(description, def)
Define an individual test case.
it(description, def)
Alternative to test(). specify is an alias.
Example
describe("Calculator", fn() {
test("adds numbers", fn() {
assert_eq(add(1, 2), 3)
})
it("subtracts numbers", fn() {
assert_eq(subtract(5, 3), 2)
})
describe("division", fn() {
it("divides numbers", fn() {
assert_eq(divide(10, 2), 5)
})
it("handles division by zero", fn() {
# Test error handling
})
})
})
Setup & Teardown
before_each(def)
Run before each test
after_each(def)
Run after each test
before_all(def)
Run once before all tests
after_all(def)
Run once after all tests
Assertions
assert_equal(expected, actual, message)
Asserts that two values are equal.
assert_equal(42, result, "should return 42")assert_equal("hello", str, "string should match")assert_equal(true, is_valid, "should be valid");
assert_true(value, message)
Asserts that a value is true.
assert_true(user.is_active, "user should be active")assert_true(contains(items, "test"), "should contain test item");
assert_false(value, message)
Asserts that a value is false.
assert_false(user.is_blocked, "user should not be blocked")assert_false(is_empty(items), "items should not be empty");
assert_contains(haystack, needle, message)
Asserts that a collection contains a specific value.
assert_contains(users, "admin", "should contain admin user")assert_contains([1, 2, 3], 2, "should contain 2");
assert_nil(value, message)
Asserts that a value is nil (null).
assert_nil(result.error, "should have no error")assert_nil(user.deleted_at, "should not be deleted");
assert_not_nil(value, message)
Asserts that a value is not nil.
assert_not_nil(user.id, "user should have an id")assert_not_nil(response.body, "response should have body");
Assertion Result
All assertion functions return a result hash with the following structure:
{
"passed": true,
"message": "description of the test",
"expected": value_that_was_expected,
"actual": value_that_was_actual
}
assert_not(condition)
Assert condition is false
assert_eq(a, b)
Assert a equals b
assert_ne(a, b)
Assert a does not equal b
Null & Comparison
assert_null(value)
Assert value is null
assert_not_null(value)
Assert value is not null
assert_gt(a, b)
Assert a > b
assert_lt(a, b)
Assert a < b
Advanced Assertions
assert_match(str, pattern)
Assert string matches regex
assert_contains(coll, val)
Assert collection contains value
assert_hash_has_key(h, k)
Assert hash has key
assert_json(string)
Assert string is valid JSON
Expect API
Chainable assertion API for expressive testing. Use expect(value) followed by chainable methods.
expect(value)
Creates an expectation with the given value. Chain with to_*() methods.
expect(42).to_equal(42);
expect("hello").to_contain("ell");
expect(10).to_be_greater_than(5);
expect(user).to_not_be_null();
Equality Assertions
expect(value).to_be(expected)
Asserts value is same as expected (identity)
expect(value).to_equal(expected)
Asserts value equals expected (value equality)
expect(value).to_not_be(expected)
Asserts value is not the same as expected
expect(value).to_not_equal(expected)
Asserts value does not equal expected
Null Assertions
expect(value).to_be_null()
Asserts value is null
expect(value).to_not_be_null()
Asserts value is not null
Comparison Assertions
expect(value).to_be_greater_than(n)
Asserts value > n
expect(value).to_be_less_than(n)
Asserts value < n
expect(value).to_be_greater_than_or_equal(n)
Asserts value >= n
expect(value).to_be_less_than_or_equal(n)
Asserts value <= n
Collection/String Assertions
expect(value).to_contain(item)
Asserts array or string contains item
expect(string).to_be_valid_json()
Asserts string is valid JSON
Test Helpers
with_transaction(block)
Begin → run block → always rollback (never commits)
freeze_time(ts)
Pin datetime_now(); cleared before each test
travel_to(ts)
Alias for freeze_time
unfreeze_time()
Restore wall-clock time
Factory Functions
Factory.define(name, data)
Define a factory with a static hash or callable block. Use #{n} in strings for auto-incrementing values.
Factory.define("user", {
"name": "Test User",
"email": "user#{n}@test.com"
})
Factory.define("post", fn() {
return {"title": "Post #{Factory.sequence("post")}"}
})
Factory.create(name)
Create an instance from a factory.
user = Factory.create("user")
println(user["name"]) # "Test User"
Factory.create_with(name, overrides)
Create an instance with custom overrides.
admin = Factory.create_with("user", { "role": "admin" })
println(admin["role"]) # "admin"
println(admin["name"]) # "Test User" (from factory)
Factory.create_list(name, count)
Create multiple instances
Factory.sequence(name)
Get auto-incrementing number
Factory.bind(name, Model)
Associate factory with a model class
Factory.insert(name, overrides?)
Build + Model.create
Factory.clear
Clear definitions, bindings, sequences
View Introspection (E2E)
After a get/post/… request that renders a view, these helpers inspect what was rendered. They report nothing for a redirect or a JSON response. See the testing guide for the full E2E flow.
assigns()
All view locals as a hash ({} if none)
assign(key)
One view local, or null
view_path()
Rendered template path, e.g. "posts/index.html" ("" if none)
render_template()
Did the response render a view? (render_template?() is an alias)
Very large locals (≈48 KB+) degrade to a keys-only assigns(): keys present, values null.
Query Assertions (E2E)
Every response records the AQL queries the request ran (from the same log behind the dev bar's N+1 badge). Assert against it to catch a query issued in a loop, or to hold an endpoint to a query budget. See the testing guide.
assert_no_n_plus_one(res)
Fails if any AQL template fired 2+ times (a loop that should be batched)
assert_query_count(res, n)
Asserts the request ran exactly n queries
assert_max_queries(res, n)
Asserts the request ran at most n queries
res["query_count"] / res["n_plus_one"]
Raw count and {query, count} groups behind the assertions
soli test --fail-on-n1
Suite-wide guard: fails any request spec that triggers an N+1, no per-test assert_no_n_plus_one needed
Browser Testing
Available in browser specs, run with soli test --browser. Positive assertions wait for the condition; negative ones check immediately. Pass {"timeout": 30} to override the 10-second default. See Browser Testing for the full guide.
visit(path)
Navigate the browser; relative paths hit this worker's test server
click(selector)
Dispatch a real mouse click at the element's position
click_link(text)
Click a link by its visible text
fill_in(field, value)
Type into a field found by selector, label, name or placeholder
select_option(select, option)
Choose an option by value or visible text
check(box) / uncheck(box)
Tick or untick a checkbox
choose(radio)
Select a radio button
press(key)
Press a key or chord, e.g. Enter or Alt+d
page_text() / page_html()
The page's visible text, or its full markup
page_path() / page_url() / page_title()
Where the browser is, and what the page is called
evaluate(expression)
Run JavaScript in the page and get the value back, types preserved
viewport(preset) / viewport(width, height)
The size the page renders at. Declared in a describe body it covers every test in the suite (nested suites inherit it); called inside a test it resizes there and then. Presets: mobile, iphone, iphone_se, android, tablet, ipad, laptop, desktop, wide — the device ones also emulate pixel ratio and touch. No arguments reads the current viewport back. Default 1280×800
screenshot(path)
Write a PNG of the current view
wait_for(selector)
Block until an element exists
wait_for_text(text)
Block until the text appears
assert_text(text)
Waits for the page to show text; assert_no_text checks immediately
assert_selector(selector)
Waits for the element; assert_no_selector checks immediately
assert_page_path(path)
Waits until the browser is at path
assert_no_page_errors()
Fails if the page threw or logged an error
page_errors()
The captured JavaScript errors, as an array