Skip to content

Assertions

Beyond simple output matching, scaf supports powerful assertions using expressions and additional queries.

Output matching checks exact values:

test "exact match" {
$userId: 1
u.name: "Alice" // Must equal "Alice"
u.age: 30 // Must equal 30
u.verified: true // Must equal true
}

Assertions allow complex expressions:

test "with assertions" {
$userId: 1
u.name: "Alice"
// Expressions - more flexible
assert {
(u.age >= 18)
(u.age < 100)
(u.verified == true)
}
}

For a single condition, use the shorthand without braces:

test "user is adult" {
$userId: 1
assert (u.age >= 18)
}

Use assert { } with one or more parenthesized expressions:

test "expression examples" {
$userId: 1
assert {
// Comparisons
(u.age >= 18)
(u.age < 100)
// Equality
(u.name == "Alice")
(u.status != "banned")
// Logical operators
(u.verified == true && u.age >= 18)
(u.role == "admin" || u.role == "moderator")
// String operations
(u.email contains "@")
(u.name startsWith "A")
(u.domain endsWith ".com")
// Length checks
(len(u.name) > 0)
(len(u.tags) >= 1)
// Null checks
(u.deletedAt == nil)
(u.profile != nil)
}
}

scaf uses expr-lang for assertions. Available features:

OperatorDescription
==Equal
!=Not equal
<Less than
>Greater than
<=Less than or equal
>=Greater than or equal
OperatorDescription
&&Logical AND
||Logical OR
!Logical NOT
OperatorDescription
+Addition
-Subtraction
*Multiplication
/Division
%Modulo
OperatorDescription
containsString contains substring
startsWithString starts with prefix
endsWithString ends with suffix
matchesRegex match
FunctionDescription
len(x)Length of string/array
abs(x)Absolute value
min(a, b)Minimum value
max(a, b)Maximum value
floor(x)Round down
ceil(x)Round up
upper(s)Uppercase string
lower(s)Lowercase string
trim(s)Trim whitespace
split(s, sep)Split string
join(arr, sep)Join array
keys(map)Map keys
values(map)Map values
assert {
// Array access
(u.tags[0] == "admin")
(len(u.tags) > 0)
// Map access
(u.metadata.theme == "dark")
(u.settings["notifications"] == true)
// Check if contains
("admin" in u.roles)
}
assert {
(u.verified ? u.status == "active" : u.status == "pending")
}
assert {
// Safe access - won't error if profile is nil
(u.profile?.bio != "")
(u.settings?.theme == "dark")
}
assert {
// Default value if nil
((u.nickname ?? "Anonymous") != "")
}

Run additional queries and assert on their results:

test "verify side effects" {
$userId: 1
u.name: "Alice"
// Run a separate query, assert on results
assert `
MATCH (p:Post {authorId: 1})
RETURN count(p) as postCount
` {
(postCount > 0)
(postCount < 100)
}
}

Reference a defined function:

fn CountPosts(authorId) `
MATCH (p:Post {authorId: $authorId})
RETURN count(p) as count
`
GetUser {
test "user has posts" {
$userId: 1
u.name: "Alice"
// Use the named function
assert CountPosts(authorId: 1) {
(count > 0)
}
}
}

Pass values from the main query to the assertion query:

test "verify created data" {
$userId: 1
u.id: 1
u.name: "Alice"
// Use u.id from main query result
assert CountPosts(authorId: u.id) {
(count >= 0)
}
}

You can mix output matching with assertions:

test "comprehensive validation" {
$userId: 1
// Exact matches
u.name: "Alice"
u.email: "alice@example.com"
// Expression assertions
assert {
(u.age >= 18)
(u.verified == true)
}
// Query assertions
assert `MATCH (s:Session {userId: 1}) RETURN count(s) as c` {
(c > 0)
}
// Named function assertion
assert CountPosts(authorId: 1) {
(count >= 0)
}
}

You can have multiple assert blocks:

test "multiple assertion groups" {
$userId: 1
u.name: "Alice"
// Age validations
assert {
(u.age >= 18)
(u.age < 120)
}
// Email validations
assert {
(u.email contains "@")
(len(u.email) > 5)
}
// Relationship validations
assert `MATCH (u:User {id: 1})-[:KNOWS]-(f) RETURN count(f) as friends` {
(friends >= 0)
}
}

When an assertion fails, scaf reports:

  • The expression that failed
  • The actual values involved
  • The assertion block location
test "user is adult" FAILED
assertion failed: u.age >= 18
u.age = 15
  1. Use output matching for exact values — It’s simpler and more readable

  2. Use assertions for ranges and conditionsu.age >= 18 is clearer than checking exact age

  3. Group related assertions — Keep validation logic organized

  4. Use query assertions for side effects — Verify data was created/modified correctly

  5. Prefer named functions — Reusable and more readable than inline queries

  6. Use shorthand for single conditionsassert (expr) is cleaner than assert { (expr) }

test "user creation" {
$name: "Alice"
$email: "alice@example.com"
// Exact match for returned values
u.name: "Alice"
u.email: "alice@example.com"
// Assertions for derived/computed values
assert {
(u.id > 0) // Auto-generated
(u.createdAt != nil) // Auto-set
}
// Verify in database
assert CountUsers() { (count >= 1) }
}