Setup & Teardown
Setup & Teardown
Section titled “Setup & Teardown”Setup blocks prepare the database state before tests run. Teardown blocks clean up after tests complete. scaf supports setup at multiple levels with inheritance.
Inline Setup
Section titled “Inline Setup”The simplest form — a raw query in backticks:
setup `CREATE (alice:User {id: 1, name: "Alice", email: "alice@example.com"})CREATE (bob:User {id: 2, name: "Bob", email: "bob@example.com"})`Multi-statement setup:
setup `// Clear existing dataMATCH (n) DETACH DELETE n
// Create test usersCREATE (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})
// Create relationshipsCREATE (alice)-[:FRIENDS_WITH]->(bob)`Teardown
Section titled “Teardown”Runs after tests complete (in reverse order of setup):
teardown `MATCH (n) DETACH DELETE n`Setup Levels
Section titled “Setup Levels”Setup can be defined at four levels:
Suite Level
Section titled “Suite Level”Runs once at the start of the file:
// This runs first, before any scopesetup `CREATE (:Config {env: "test"})CREATE (alice:User {id: 1, name: "Alice"})CREATE (bob:User {id: 2, name: "Bob"})`
GetUser { test "uses suite data" { $userId: 1 u.name: "Alice" // Created by suite setup }}Scope Level
Section titled “Scope Level”Runs for each function scope:
GetUser { // Runs before any test in this scope setup ` CREATE (:Session {userId: 1, token: "abc123"}) `
test "user has session" { ... } test "another test" { ... }}
CreateUser { // Different setup for this scope setup ` CREATE (:Sequence {name: "user_id", value: 100}) `
test "creates user" { ... }}Group Level
Section titled “Group Level”Runs for each group:
GetUser { group "with posts" { setup ` CREATE (:Post {id: 1, authorId: 1, title: "First Post"}) CREATE (:Post {id: 2, authorId: 1, title: "Second Post"}) `
test "user has posts" { ... } test "post count" { ... } }
group "without posts" { // No setup — user has no posts test "new user" { ... } }}Test Level
Section titled “Test Level”Runs only for a specific test:
CountUsers { test "base count" { count: 2 }
test "with extra user" { setup `CREATE (:User {id: 999, name: "Temp"})`
count: 3 }
test "back to base count" { // Previous test's setup rolled back count: 2 }}Setup Inheritance
Section titled “Setup Inheritance”Tests inherit setup from all ancestor levels. Execution order:
Suite Setup → Scope Setup → Group Setup → Test SetupExample:
// 1. Suite setupsetup `CREATE (:App {name: "test"})`
GetUser { // 2. Scope setup setup `CREATE (:User {id: 1, name: "Alice"})`
group "with profile" { // 3. Group setup setup `CREATE (:Profile {userId: 1, bio: "Hello"})`
test "full setup chain" { // 4. Test setup setup `CREATE (:Preference {userId: 1, theme: "dark"})`
// All four setups have run at this point $userId: 1 u.name: "Alice" } }}Named Setup
Section titled “Named Setup”Reference setups defined elsewhere:
Module Setup
Section titled “Module Setup”Run an imported module’s setup clause:
import fixtures "./shared/fixtures"
// Use module setup (runs the module's setup clause)setup fixturesFunction Call
Section titled “Function Call”Call a specific function from an imported module:
import fixtures "./shared/fixtures"
// Call function with parameters (no $ prefix)setup fixtures.CreateUsers()With Parameters
Section titled “With Parameters”import fixtures "./shared/fixtures"
// Pass parameters to setup (no $ prefix on param names)setup fixtures.CreatePosts(count: 10, authorId: 1)
GetUser { setup fixtures.AddFriends(userId: 1, friendIds: [2, 3, 4])
test "user with friends" { $userId: 1 // ... }}Block Setup
Section titled “Block Setup”Combine multiple setup calls:
setup { fixtures // run module's setup clause fixtures.CreateUser(id: 1, name: "Test") `CREATE (:Extra)` // inline query}Defining Named Setups
Section titled “Defining Named Setups”In shared/fixtures.scaf:
fn CreateUsers() `CREATE (alice:User {id: 1, name: "Alice"})CREATE (bob:User {id: 2, name: "Bob"})`
fn CreatePosts(count: int, authorId: int) `UNWIND range(1, $count) as iCREATE (:Post {id: i, authorId: $authorId, title: "Post " + i})`
fn AddFriends(userId: int, friendIds: [int]) `MATCH (u:User {id: $userId})UNWIND $friendIds as friendIdMATCH (f:User {id: friendId})CREATE (u)-[:FRIENDS_WITH]->(f)`Transaction Behavior
Section titled “Transaction Behavior”Default: Rollback
Section titled “Default: Rollback”By default, each test runs in a transaction that rolls back:
CountUsers { test "creates temp user - rolls back" { setup `CREATE (:User {id: 999})` count: 3 // Sees temp user }
test "temp user is gone" { count: 2 // Transaction rolled back }}Explicit Teardown
Section titled “Explicit Teardown”For persistent cleanup:
setup `// Create data that persistsCREATE (:PersistentNode)`
teardown `// Explicitly clean upMATCH (n:PersistentNode) DELETE n`Complex Setup Patterns
Section titled “Complex Setup Patterns”Conditional Data
Section titled “Conditional Data”Use multiple groups with different setup:
GetUser { group "verified users" { setup `CREATE (:User {id: 1, verified: true})`
test "verified flag is true" { $userId: 1 u.verified: true } }
group "unverified users" { setup `CREATE (:User {id: 1, verified: false})`
test "verified flag is false" { $userId: 1 u.verified: false } }}Incremental Setup
Section titled “Incremental Setup”Build on previous setup:
setup `CREATE (:User {id: 1, name: "Alice"})`
GetUser { setup ` MATCH (u:User {id: 1}) SET u.verified = true `
group "with posts" { setup ` MATCH (u:User {id: 1}) CREATE (u)-[:AUTHORED]->(:Post {title: "Post 1"}) `
test "user has verified status and posts" { $userId: 1 u.verified: true } }}Best Practices
Section titled “Best Practices”-
Keep setup focused — Only set up what’s needed for the tests
-
Use inheritance — Move common setup to higher levels
-
Prefer transactions — Let rollback handle cleanup
-
Name setups clearly — If importing, use descriptive function names
-
Document complex setup — Add comments for non-obvious data
// Good: Clear, minimal, commentedsetup `// Base users for all testsCREATE (alice:User {id: 1, name: "Alice", role: "admin"})CREATE (bob:User {id: 2, name: "Bob", role: "user"})// Admin has special permissionsCREATE (alice)-[:HAS_PERMISSION]->(:Permission {name: "delete"})`
GetUser { // Only add what this scope needs setup `CREATE (:Session {userId: 1})`
group "with activity" { // Only add what this group needs setup `CREATE (:Login {userId: 1, time: timestamp()})`
test "has recent login" { ... } }}