Writing unit tests for obelisk using the test-helpers library. Covers test helper crud-txn, expected result construction, and failure testing patterns.
Scanned 9/9/2026
Install to Claude Code
npx -y skills add jackfoxy/obelisk --skill obelisk-test --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Obelisk Test?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jackfoxy-obelisk-test)More formats (shields.io, HTML) on the badges page.
---
name: obelisk-test
description: Writing unit tests for obelisk using the test-helpers library. Covers test helper crud-txn, expected result construction, and failure testing patterns.
user-invocable: true
disable-model-invocation: false
validated: safe
checked-by: ~nomryg-nilref
---
# obelisk-test skill
Write unit tests for obelisk database operations using the test-helpers library at `desk/lib/test-helpers.hoon`. Tests live in `desk/tests/lib/`.
## File structure
Every test file:
1. Imports test-helpers: `/+ *test-helpers`
2. Opens a core: `|%`
3. Optionally defines reusable data (table DDL, insert statements, expected row sets) as arms before the test arms
4. Each test arm is named `++ test-<feature>-<nn>` for success tests or `++ test-fail-<feature>-<nn>` for failure tests
5. Closes with `--`
Some test files define a local `bowl` and `state` in a door before `|%` (see `delete.hoon`). Most test files rely on the `bowl` and `state` from test-helpers directly.
## Test helper naming convention: `exec-<actions>-<resolves>`
The helper name encodes the test shape:
- **actions** = number of setup/mutation pokes *after* init (0, 1, 2, ... 9)
- **resolves** = number of verification pokes at the end (r, l, 1, 2, ... 6)
Resolve suffixes:
- `r` = single `cmd-result` comparison via `eval-results`
- `l` = single `(list cmd-result)` comparison via `expect-eq`
- `1`, `2`, ... `6` = that many `cmd-result` comparisons via `eval-results` (welded/zinged)
- `ls` = first resolve is `(list cmd-result)`, second is `cmd-result`
- `ll` = both resolves are `(list cmd-result)`
### Common helpers and when to use them
| Helper | Shape | Use case |
|--------|-------|----------|
| `exec-0-r` | init, compare cmd-result only | DDL-only tests (CREATE DATABASE, CREATE TABLE) |
| `exec-0-l` | init, compare (list cmd-result) | Multi-result init |
| `exec-0-1` | init + 1 resolve | Schema setup then query |
| `exec-0-02` | 2 resolves (no init poke) | Two independent queries |
| `exec-1-1` | init + 1 action + 1 resolve | Setup, mutate, verify |
| `exec-1-2` | init + 1 action + 2 resolves | Setup, mutate, verify two things |
| `exec-2-1` | init + 2 actions + 1 resolve | Setup, two mutations, verify |
| `exec-2-2` | init + 2 actions + 2 resolves | Setup, two mutations, two verifications |
| `exec-3-1` | init + 3 actions + 1 resolve | Three setup steps then verify |
| `exec-5-1` | init + 5 actions + 1 resolve | Complex multi-step setup |
### Failure helpers
| Helper | Shape | Use case |
|--------|-------|----------|
| `failon-0` | init should crash | Init poke expected to fail |
| `failon-1` | init + action that should crash | Setup then failing action |
| `failon-1c` | init + action (raw `action` type) crashes | Failing typed action poke |
| `failon-1cc` | init (raw action) + action (raw action) crashes | Both pokes use raw action type |
| `failon-c` | init + action (embedded `=action`) crashes | Variant with embedded action face |
| `failon-2` | init + 1 action + failing 2nd action | Two actions, second fails |
| `failon-2c` | init (raw) + 1 action (raw) + failing action (raw) | All raw action types |
| `failon-3` | init + 2 actions + failing 3rd action | Three actions, third fails |
| `failon-3c` | init + 2 actions + failing 3rd (raw action) | Mixed tape/raw |
| `failon-4` | init + 3 actions + failing 4th action | Four actions, fourth fails |
### Debug helpers
Replace `exec-` with `debug-` (e.g., `debug-0-1`, `debug-2-1`) to get crash output instead of comparison. These use `%test` mark and `expect-fail-message` with `'placeholder for debugging'`.
## Poke tuple format
Each poke (init, action, resolve) is a triple:
```hoon
[tmsp=@da db=@tas uql=tape]
```
- `tmsp`: timestamp for the bowl (simulates server time)
- `db`: database name (use `%sys` for system-level commands like CREATE DATABASE, `%db1` for database-scoped commands)
- `uql`: the urQL command string as a tape
For raw action pokes (in `failon-1c`, `failon-1cc`, `exec-0-0c2`, `exec-5-2xx`):
```hoon
[tmsp=@da cmds=action]
```
## Constructing expected results
### `cmd-result` structure
A `cmd-result` is a tagged union with `%results` head followed by a list of result entries:
```hoon
:- %results
:~ [%action 'SELECT'] :: or 'INSERT INTO db1.dbo.my-table', etc.
[%result-set <list of vectors>] :: only for SELECT results
[%server-time ~2012.5.3]
[%relation-name 'db1.dbo.my-table'] :: source table(s)
[%schema-time ~2012.5.1]
[%data-time ~2012.5.2]
[%vector-count 3]
==
```
For JOINed queries, each source table gets its own message/schema-time/data-time block:
```hoon
[%relation-name 'db1.dbo.calendar']
[%schema-time ~2012.4.30]
[%data-time ~2012.4.30]
[%relation-name 'db1.dbo.holiday-calendar']
[%schema-time ~2012.4.30]
[%data-time ~2012.4.30]
```
### Result vectors
Each row is a `%vector` containing a list of `[column-name [aura value]]` cells:
```hoon
:- %vector
:~ [%col1 [~.t 'cord']]
[%col2 [~.p ~nomryg-nilref]]
[%col3 [~.ud 20]]
[%col4 [~.da ~2010.6.1]]
==
```
Aura tags use `~.` prefix: `~.t` for `@t`, `~.da` for `@da`, `~.p` for `@p`, `~.ud` for `@ud`, etc.
Default values when not explicitly set: `@da` defaults to `~2000.1.1`, `@t` defaults to `'Default'`, `@p` defaults to `~zod`, `@ud` defaults to `0`.
### INSERT result pattern
INSERT results do not contain a `%result-set`. Instead:
```hoon
:- %results
:~ [%action 'INSERT INTO db1.dbo.my-table']
[%server-time ~2012.5.3]
[%schema-time ~2012.5.1]
[%data-time ~2012.5.1]
[%message 'inserted:']
[%vector-count 2]
[%message 'table data:']
[%vector-count 2]
==
```
### Failure result pattern
For `failon-*` helpers, the `expect` argument is a `@t` cord with the expected error message:
```hoon
%- crip "%date is duplicate column name in "
"common table expression %my-cte"
```
Or simply:
```hoon
'expected error message'
```
## Complete test examples
### Simple query test (exec-0-1: init then resolve)
```hoon
++ test-cte-00
=| run=@ud
%- exec-0-1
:* run
:+ ~2012.4.30
%db1
%- zing :~ "CREATE DATABASE db1;"
create-table
insert-table
==
::
:+ ~2012.5.3
%db1
"FROM my-table SELECT *"
::
:- %results :~ [%action 'SELECT']
[%result-set expected-rows]
[%server-time ~2012.5.3]
[%relation-name 'db1.dbo.my-table']
[%schema-time ~2012.4.30]
[%data-time ~2012.4.30]
[%vector-count 7]
==
==
```
### Insert + verify test (exec-1-2: init + action + 2 resolves)
```hoon
++ test-insert-01
=| run=@ud
%- exec-1-2
:* run
[~2012.4.30 %sys "CREATE DATABASE db1"]
::
:+ ~2012.5.1
%db1
"CREATE TABLE db1..my-table ".
"(col1 @t, col2 @p, col3 @ud) ".
"PRIMARY KEY (col1)"
::
:+ ~2012.5.3
%db1
"INSERT INTO db1..my-table (col1, col2, col3) ".
"VALUES ('cord',~nomryg-nilref,20) ('Default',Default, 0)"
::
[~2012.5.4 %db1 "FROM my-table SELECT *"]
::
:- %results :: expect-1 (INSERT result)
:~ [%action 'INSERT INTO db1.dbo.my-table']
[%server-time ~2012.5.3]
[%schema-time ~2012.5.1]
[%data-time ~2012.5.1]
[%message 'inserted:']
[%vector-count 2]
[%message 'table data:']
[%vector-count 2]
==
::
:- %results :: expect-2 (SELECT result)
:~ [%action 'SELECT']
[%result-set expected-2-rows]
[%server-time ~2012.5.4]
[%relation-name 'db1.dbo.my-table']
[%schema-time ~2012.5.1]
[%data-time ~2012.5.3]
[%vector-count 2]
==
==
```
### Failure test (failon-1: init + failing action)
```hoon
++ test-fail-cte-00
=| run=@ud
%- failon-1 :* run
:+ ~2012.4.30
%db1
%- zing :~ "CREATE DATABASE db1;"
create-calendar
insert-calendar
==
::
:+ ~2012.5.5
%db1
"FROM bad-table SELECT *"
::
'expected error message'
==
```
## Combining multiple urQL statements in init
Use `zing` to concatenate multiple urQL statements (each ending with `;`) into a single tape for the init poke:
```hoon
:+ ~2012.4.30
%db1
%- zing :~ "CREATE DATABASE db1;"
create-table
insert-table
==
```
## Reusable DDL/DML arms
Define reusable DDL and DML as tape arms at the top of the test file:
```hoon
++ create-table "CREATE TABLE db1..my-table ".
"(col0 @da, col1 @t, col2 @p) ".
"PRIMARY KEY (col0, col2);"
::
++ insert-table "INSERT INTO db1..my-table (col0, col1, col2) ".
"VALUES (~2010.5.3, 'cord', ~nomryg-nilref);"
```
Note: DDL/DML arms that will be `zing`ed into an init must end with `;` followed by a space or be followed by statements that do.
## Timestamp conventions
- Init timestamps typically start at `~2012.4.30`
- Subsequent action timestamps increment: `~2012.5.1`, `~2012.5.2`, `~2012.5.3`, etc.
- The `schema-time` in expected results matches when the schema was created
- The `data-time` matches when data was last modified
- The `server-time` matches the resolve poke's timestamp
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!