Testing Events
This page shows two ways to run integration tests: against a Causet Cloud test fork (available today, via the CLI) and against a self-hosted local Docker stack (team contributors with private source — see Installation). The CLI/fork examples below work on Cloud today.
Causet does not have a unit test framework for rules — testing happens at the integration level against a running stack. The compiler provides the first line of validation.
The Compiler as First Test
Before any test runs, the Causet compiler validates your application:
- Event types are declared before being referenced in actions, projections, or listeners.
- Action
emitoperations reference valid event types. - Projection
source_eventsreference valid event types. deriveexpression field references exist in the referenced event’s payload.- Reserved field names (
type,ts,entity_id) are not declared in payloads. - Expression syntax is valid.
- No circular dependencies in state or event references.
Run the compiler as part of CI:
causet build compile --runtime . --out dist/
# Exit 0 = valid. Exit non-zero = compiler errors with locations.A passing compile gives you schema correctness without running a single test.
Integration Test Pattern
The standard testing pattern for Causet:
- Create (or reuse) a disposable test fork on Causet Cloud.
- Deploy your application IR to that fork.
- Submit intents.
- Wait for projections.
- Assert projection rows via query.
- Delete the fork.
Step 1: Create a test fork
causet fork create test-run-001 --parent mainStep 2: Deploy to the test fork
causet build compile --runtime . --out dist/
causet deploy --fork test-run-001 --runtime . --fullStep 3: Submit an intent
causet intent FOLLOW_ARTIST \
--fork test-run-001 \
--stream user_stream \
--entity user-test-001 \
--payload '{"user_id":"user-test-001","artist_id":"artist-test-001"}'Step 4: Assert entity/ledger state
causet inspect entity user-test-001 --fork test-run-001 --stream user_stream
causet inspect timeline --fork test-run-001 --stream user_stream --entity user-test-001Step 5: Wait for projection, then query
Projections are async. Poll the query until the row appears (typically well under a second):
until causet query artist_followers --fork test-run-001 --param user_id=user-test-001 \
--json | grep -q "user-test-001"; do sleep 0.2; done
causet query artist_followers --fork test-run-001 --param user_id=user-test-001
# Expected: {"rows": [{"user_id": "user-test-001", "artist_id": "artist-test-001", "followed_at": ...}]}Step 6: Clean up
causet fork delete test-run-001Testing Preflight Rejection
Submit an intent that should be rejected by preflight:
causet intent FOLLOW_ARTIST \
--fork test-run-001 \
--stream user_stream \
--entity user-test-002 \
--payload '{"user_id":"user-test-002","artist_id":"user-test-002"}'
# Expected response:
# {
# "status": "REJECTED",
# "code": "CANNOT_FOLLOW_SELF",
# "message": "A user cannot follow themselves."
# }Assert no event was written by checking the timeline is unchanged:
causet inspect timeline --fork test-run-001 --stream user_stream --entity user-test-002
# Expected: empty — no ARTIST_FOLLOWED eventTesting Projection Failure
Inject an event that will fail the projection handler (e.g. by temporarily breaking the projection definition and deploying it):
- Introduce a
deriveexpression error in your projection DSL. - Compile and deploy the broken IR to the test fork.
- Submit an intent that triggers the broken projection.
- Confirm the query no longer returns the expected row (the event committed, but the projection failed to materialize it).
A dedicated DLQ/failure inspection CLI (causet dlq, causet failures) is proposed and not shipped today — see Projection Failures. For now, detect projection failures by comparing query results against the entity/ledger state from causet inspect.
Testing Multi-Step Flows
For multi-step flows (submit chains, see Your First Workflow), submit the sequence of intents and verify state transitions with causet inspect entity after each step:
causet intent SAVE_SHOW_DRAFT --fork test-run-001 --stream artist_stream --entity artist-1 --payload '...'
causet inspect entity artist-1 --fork test-run-001 --stream artist_stream
# Expect status: "draft", then after the auto-submitted PUBLISH_SHOW commits:
causet inspect entity artist-1 --fork test-run-001 --stream artist_stream
# Expect status: "published"Testing Multiple Events in Sequence
Test idempotency and state accumulation:
# Follow
causet intent FOLLOW_ARTIST --fork test-run-001 --stream user_stream --entity u1 --payload '{"user_id":"u1","artist_id":"a1"}'
# Unfollow
causet intent UNFOLLOW_ARTIST --fork test-run-001 --stream user_stream --entity u1 --payload '{"user_id":"u1","artist_id":"a1"}'
# Follow again
causet intent FOLLOW_ARTIST --fork test-run-001 --stream user_stream --entity u1 --payload '{"user_id":"u1","artist_id":"a1"}'
# Assert: artist_followers should have one row (not three)
causet query artist_followers --fork test-run-001 --param user_id=u1
# Expected: {"rows": [{"user_id": "u1", "artist_id": "a1", ...}]}Isolation Between Tests
Use a unique fork per test run (or test suite) to avoid cross-test contamination — each fork gets its own isolated schema:
FORK_ID="test-$(date +%s)"
causet fork create "$FORK_ID" --parent main
causet deploy --fork "$FORK_ID" --runtime . --full
# run tests against --fork "$FORK_ID"
causet fork delete "$FORK_ID"No Unit Test Framework
There is currently no framework for unit-testing individual rules in isolation (without a deployed IR). Rule evaluation requires a loaded IR and an entity snapshot — the same dependencies as a running fork.
The recommended approach is fast integration tests against a disposable Causet Cloud fork, as shown above. Once the self-hosted runtime ships (see Installation), the same pattern will also work against a local Docker stack.
Related Pages
- Concepts: Intents — intent structure for test submissions
- Concepts: Projections — projection materialization timing
- Concepts: Replay — rebuilding state between test runs