> Full SurrealDB documentation index: https://surrealdb.com/docs/llms.txt

# Testing

SurrealKit includes a built-in testing framework for validating schema correctness, permissions, and API behaviour across multiple actor types.

SurrealKit includes a testing framework that lets you write declarative test suites for your SurrealDB schema. Tests run against an isolated ephemeral database per suite, so they are safe to run in any environment without affecting persistent data.

## Running tests

```bash
surrealkit test
```

SurrealKit reads all suite files from `database/tests/suites/*.toml`, runs them in parallel (configurable), and exits non-zero if any case fails.

## Project structure

```text
database/tests/
├── config.toml          # global defaults
└── suites/
    ├── security.toml
    └── api.toml
```

### Global config

`database/tests/config.toml` sets defaults shared across all suites:

```toml
[defaults]
timeout_ms = 10000
base_url = "http://localhost:8000"

[actors.root]
kind = "root"
```

Suite files are parsed strictly: an unknown key in a case, an actor, or the global config is an error rather than being ignored, so a typo fails the run instead of quietly disabling a check.

> [!NOTE]
> `surrealkit test` applies a filesystem sync before running, so the [empty source set check](/docs/manage/schema-migration/sync.md#empty-source-sets) applies to it. A suite whose fixtures own the complete schema, with nothing in `database/schema`, needs `--no-sync`.

## Test types

SurrealKit supports five test types, specified via the `kind` field on each test case.

### `sql_expect`

Runs a SurrealQL statement and asserts whether it succeeds or fails:

```toml
[[cases]]
name = "guest_cannot_create_order"
kind = "sql_expect"
actor = "guest"
sql = "CREATE order CONTENT { total: 10 };"
allow = false
error_contains = "permission"
```

Optional `assertions` check the returned data:

```toml
[[cases]]
name = "user_sees_own_profile"
kind = "sql_expect"
actor = "user_alice"
sql = "SELECT * FROM user WHERE id = $auth.id;"
allow = true

[[cases.assertions]]
path = "0.id"
equals_auth = "$auth.id"
```

#### Missing paths fail

_(since v1.0)_

An assertion whose `path` is not present in the result fails with `path '<path>' not found`, rather than the comparison being skipped. The same applies to a `header_assertions` entry naming a header the response never sent. This catches typos and queries that matched zero records.

To assert that a value is genuinely absent, state it explicitly. `exists = false` is the only specification that passes on a missing path:

```toml
[[cases.assertions]]
path = "0.secret"
exists = false
```

Paths are dot-separated. A segment that parses as a number indexes an array, and anything else keys an object, so `0.owner.name` reaches the owner name of the first record. There is no bracket syntax, no `$.` prefix, and no escaping, so a field whose name contains a dot cannot be addressed.

> [!IMPORTANT]
> Assertions that go red after upgrading from 0.7 were most likely never being evaluated, because a missing path used to pass. Check the shape of the actual result before changing the assertion. See [Upgrading](/docs/manage/schema-migration/upgrading.md#if-you-use-the-tester).

### `permissions_matrix`

Validates that a single actor has the expected create / select / update / delete permissions on a table or record:

```toml
[[cases]]
name = "reader_cannot_modify_orders"
kind = "permissions_matrix"
actor = "reader"
table = "order"
record_id = "order:test"

[[cases.rules]]
action = "select"
allow = true

[[cases.rules]]
action = "update"
allow = false
error_contains = "permission"
```

> [!NOTE]
> Permission probing was reworked in 1.0 to handle row-level security, where a filtered result cannot be told apart from a rejected one. Create probes recreate a record with the same fields, update probes write a marker field first, records created during the matrix are cleaned up afterwards, and a permission that skips is now reported differently from one that throws. An existing matrix can report a different verdict as a result.

### `schema_metadata`

Asserts structural facts about the schema: that a field exists with a given type, that an index is defined, and so on.

### `schema_behavior`

Tests computed fields, functions, and record relations by asserting on the values returned after specific operations.

### `api_request`

Tests HTTP API endpoints, useful when your SurrealDB instance exposes a custom API layer:

```toml
[[cases]]
name = "orders_endpoint_returns_200"
kind = "api_request"
actor = "root"
method = "GET"
path = "/api/orders"
expected_status = 200

[[cases.body_assertions]]
path = "0.id"
exists = true
```

## Actors

Each test case runs as a named actor with a specific authentication method. Actors are defined in `config.toml` or at the suite level.

| Actor kind | When to use |
|---|---|
| `root` | Full root-level access |
| `database` | Database-level user credentials |
| `record` | Record access via signup / signin |
| `token` | JWT token from an environment variable |
| `headers` | Custom HTTP headers (e.g. tenant ID) |

```toml
[actors.user_alice]
kind = "record"
access = "app_access"

[actors.user_alice.signin_params]
email = "alice@example.com"
password = "secret"

[actors.tenant_a]
kind = "headers"
headers = { "x-tenant-id" = "tenant_a" }
```

## Filtering

| Flag | Description |
|---|---|
| `--suite <glob>` | Run only suites whose name matches the glob |
| `--case <glob>` | Run only cases whose name matches the glob |
| `--tag <tag>` | Run only cases tagged with the given tag (repeatable) |
| `--fail-fast` | Stop on the first failure |
| `--parallel <N>` | Number of parallel execution threads |

## Debugging

| Flag | Description |
|---|---|
| `--keep-db` | Preserve the ephemeral database after the run for manual inspection |
| `--no-sync` | Skip the schema sync phase before running tests |
| `--no-seed` | Skip the seeding phase before running tests |
| `--json-out <path>` | Write a machine-readable JSON report to the specified file |

## Next steps

- [CI / CD](/docs/manage/schema-migration/testing/ci-cd.md): integrate tests into automated pipelines with GitHub Actions and Docker Compose

## Learn more about this topic

- [Schema migrations in SurrealDB: A local dev workflow](https://surrealdb.com/blog/schema-migrations-in-surrealdb-a-local-dev-workflow#seeding) (Blog, section "Seeding")
- [7: Managing the schema with SurrealKit](https://surrealdb.com/learn/movies/surrealkit#testing-the-database) (SurrealDB University, section "Testing the database")
- [13: Events and CI](https://surrealdb.com/learn/schemas/page-13#keep-events-with-the-schema) (SurrealDB University, section "Keep events with the schema")
