Assertions
Assertions
Section titled “Assertions”Beyond simple output matching, scaf supports powerful assertions using expressions and additional queries.
Output Matching vs Assertions
Section titled “Output Matching vs Assertions”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) }}Expression Assertions
Section titled “Expression Assertions”Shorthand Form
Section titled “Shorthand Form”For a single condition, use the shorthand without braces:
test "user is adult" { $userId: 1 assert (u.age >= 18)}Block Form
Section titled “Block Form”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) }}Expression Language
Section titled “Expression Language”scaf uses expr-lang for assertions. Available features:
Comparison Operators
Section titled “Comparison Operators”| Operator | Description |
|---|---|
== | Equal |
!= | Not equal |
< | Less than |
> | Greater than |
<= | Less than or equal |
>= | Greater than or equal |
Logical Operators
Section titled “Logical Operators”| Operator | Description |
|---|---|
&& | Logical AND |
|| | Logical OR |
! | Logical NOT |
Arithmetic Operators
Section titled “Arithmetic Operators”| Operator | Description |
|---|---|
+ | Addition |
- | Subtraction |
* | Multiplication |
/ | Division |
% | Modulo |
String Operators
Section titled “String Operators”| Operator | Description |
|---|---|
contains | String contains substring |
startsWith | String starts with prefix |
endsWith | String ends with suffix |
matches | Regex match |
Built-in Functions
Section titled “Built-in Functions”| Function | Description |
|---|---|
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 |
Collection Access
Section titled “Collection Access”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)}Ternary Operator
Section titled “Ternary Operator”assert { (u.verified ? u.status == "active" : u.status == "pending")}Optional Chaining
Section titled “Optional Chaining”assert { // Safe access - won't error if profile is nil (u.profile?.bio != "") (u.settings?.theme == "dark")}Nil Coalescing
Section titled “Nil Coalescing”assert { // Default value if nil ((u.nickname ?? "Anonymous") != "")}Query Assertions
Section titled “Query Assertions”Run additional queries and assert on their results:
Inline Query
Section titled “Inline Query”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) }}Named Function
Section titled “Named Function”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) } }}Function with Field References
Section titled “Function with Field References”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) }}Combining Output and Assertions
Section titled “Combining Output and Assertions”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) }}Multiple Assert Blocks
Section titled “Multiple Assert Blocks”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) }}Assertion Failure Messages
Section titled “Assertion Failure Messages”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 = 15Best Practices
Section titled “Best Practices”-
Use output matching for exact values — It’s simpler and more readable
-
Use assertions for ranges and conditions —
u.age >= 18is clearer than checking exact age -
Group related assertions — Keep validation logic organized
-
Use query assertions for side effects — Verify data was created/modified correctly
-
Prefer named functions — Reusable and more readable than inline queries
-
Use shorthand for single conditions —
assert (expr)is cleaner thanassert { (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) }}test "user creation" { $name: "Alice" $email: "alice@example.com"
// Unnecessary assertion for exact value assert { (u.name == "Alice") }
// Everything inline assert `MATCH (u:User) WHERE u.name = "Alice" RETURN u.id as id, u.email as email, count(*) as c` { (id > 0) (email == "alice@example.com") (c == 1) }}