Skip to content

Quick Start

This guide walks you through writing your first scaf test file. We’ll test a simple user lookup query.

  • scaf CLI installed (Installation)
  • A running Neo4j database (or other supported database)
  • A .scaf.yaml config file
  1. Create the test file

    Create users.scaf:

    // Define the query we want to test
    fn GetUser `
    MATCH (u:User {id: $userId})
    RETURN u.name, u.email, u.age
    `
  2. Add global setup

    Set up test data that all tests will use:

    setup `
    MATCH (n) DETACH DELETE n
    CREATE (alice:User {id: 1, name: "Alice", email: "alice@example.com", age: 30})
    CREATE (bob:User {id: 2, name: "Bob", email: "bob@example.com", age: 25})
    `
    teardown `
    MATCH (n) DETACH DELETE n
    `
  3. Write your first test

    GetUser {
    test "finds Alice by id" {
    // Input parameter
    $userId: 1
    // Expected outputs
    u.name: "Alice"
    u.email: "alice@example.com"
    u.age: 30
    }
    }
  4. Run the test

    Terminal window
    scaf test users.scaf

Here’s the full file:

users.scaf
// Query definitions
fn GetUser `
MATCH (u:User {id: $userId})
RETURN u.name, u.email, u.age
`
fn CountUsers `
MATCH (u:User)
RETURN count(u) as count
`
// Global setup - runs once before all tests
setup `
MATCH (n) DETACH DELETE n
CREATE (alice:User {id: 1, name: "Alice", email: "alice@example.com", age: 30})
CREATE (bob:User {id: 2, name: "Bob", email: "bob@example.com", age: 25})
`
// Global teardown - runs after all tests
teardown `
MATCH (n) DETACH DELETE n
`
// Test scope for GetUser query
GetUser {
test "finds Alice by id" {
$userId: 1
u.name: "Alice"
u.email: "alice@example.com"
u.age: 30
}
test "finds Bob by id" {
$userId: 2
u.name: "Bob"
u.age: 25
}
test "returns null for non-existent user" {
$userId: 999
u.name: null
u.email: null
}
}
// Test scope for CountUsers query
CountUsers {
test "counts all users" {
count: 2
}
}

Organize related tests with groups:

GetUser {
group "existing users" {
test "finds Alice" {
$userId: 1
u.name: "Alice"
}
test "finds Bob" {
$userId: 2
u.name: "Bob"
}
}
group "edge cases" {
test "missing user" {
$userId: 999
u.name: null
}
test "invalid id type" {
$userId: -1
u.name: null
}
}
}

For complex validations, use assert blocks:

GetUser {
test "user is a verified adult" {
$userId: 1
u.name: "Alice"
// Expression assertions
assert {
(u.age >= 18)
(u.email contains "@")
}
}
test "verify user count with query" {
$userId: 1
u.name: "Alice"
// Query assertion - runs a separate query
assert CountUsers() { count == 2 }
}
}

Add setup specific to individual tests:

CountUsers {
test "counts base users" {
count: 2
}
test "counts with temp user" {
// This setup only runs for this test
setup `
CREATE (:User {id: 999, name: "Temp"})
`
count: 3
}
test "temp user was rolled back" {
// Previous test's setup was in a transaction that rolled back
count: 2
}
}
Terminal window
# Run all .scaf files in current directory
scaf test
# Run specific file
scaf test users.scaf
# Run files matching pattern
scaf test ./queries/*.scaf
$ scaf test users.scaf
.....
5 passed, 0 failed, 0 skipped (0.05s)

With verbose output:

$ scaf test users.scaf --format=verbose
GetUser/finds Alice by id ... passed (0.01s)
GetUser/finds Bob by id ... passed (0.01s)
GetUser/returns null for non-existent user ... passed (0.01s)
CountUsers/counts all users ... passed (0.01s)
CountUsers/counts with temp user ... passed (0.01s)
5 passed, 0 failed, 0 skipped (0.05s)

When a test fails:

$ scaf test users.scaf --format=verbose
GetUser/finds Alice by id ... FAILED (0.01s)
field: u.name
expected: "Wrong Name"
actual: "Alice"
0 passed, 1 failed, 0 skipped (0.02s)