Testing
What you can test locally today — build validation, route handlers under the test runtime, and observation mode for the rest.
Extensions have three verification layers today. Know which one covers what, because they’re not interchangeable.
1. backfill build — static validation
backfill build catches the whole class of wiring mistakes before anything
is uploaded: manifest parse errors, unknown entities in permissions or hook
paths, missing default exports, undeclared HTTP hosts, invalid custom-field
declarations, oversized bundles. Run it as your compile step; it’s fast and
its errors name the file and the fix.
2. Unit tests — route handlers and pure logic
The test runtime in @backfill-io/sdk/testing installs in-memory
implementations of the standard library (Http, Settings, Secrets,
Log, Entity, sync, ingest, Crypto, verify, OAuth) and can
invoke route handlers directly. It’s documented on the
connector testing page, and despite the name it
exercises plain extension API routes the same way — skip
the connector-specific options and assert on runtime.logs,
runtime.http.calls, and your handler’s response:
import { test } from "node:test";
import assert from "node:assert/strict";
import { connectorTestRuntime } from "@backfill-io/sdk/testing";
import { GET } from "../src/api/summary";
test("summary route reads settings", async () => {
const runtime = connectorTestRuntime({
settings: { region: "us-east" },
});
const response = await runtime.call(GET, { searchParams: {} });
assert.equal(response.status, 200);
});
Business logic that doesn’t touch the standard library — payload mapping,
calculations, date math — needs no runtime at all. Keep it in plain
functions under src/lib/ and test it directly; this is where most of your
test coverage should live.
3. Hooks — observation mode is the test bed
There is currently no local runtime for hooks: a hook’s HookContext
(the entity snapshot, operation, actor) is produced by the platform, and the
SDK does not yet ship a mock for it. Structure hooks as thin adapters —
extract the context fields, call a tested src/lib/ function, act on the
result — so the untested surface stays a few lines.
Then verify the wiring against real traffic without risk:
backfill deploy --observe # run against live events, effects intercepted
backfill logs --follow # watch executions + would-have-happened effects
backfill trigger # no args: lists the deployed hooks and their ids
backfill trigger --hook <id> --data @fixtures/invoice.json
backfill promote # flip to live once the observed effects look right
In observation mode every write your hook would perform (entity mutations,
ingests, outbound HTTP) is recorded as an intercepted effect in the logs
instead of committed — a deploy-time dry run. backfill trigger invokes a
hook on demand with a fixture payload so you don’t have to wait for a real
event.
Jobs
Jobs are plain exported functions, so the split is the same as hooks: test
the logic as pure functions; use backfill job run <name> against an
observation deploy to verify the wiring and schedule registration.