Local testing
Hermetic Databricks tests with no workspace — DuckDB execution contexts, client-boundary mocks, fixtures, and cross-engine parity.
Status: Available —
@fabricorg/databricks-testkitprovides the DuckDB context, fixtures, golden files, the mock client, and cross-engine parity suites. The live half of parity runs whenDBX_TEST_LIVE=1is set.
The local tier runs your Databricks-bound logic with no workspace, no credentials, and no network — fast enough for watch mode and every CI run.
DuckDB execution context
import { createDuckDbContext } from '@fabricorg/databricks-testkit'
const ctx = await createDuckDbContext()
await ctx.loadFixture({
table: 'exposures',
ndjsonFile: 'fixtures/exposures.ndjson',
})
const rows = await ctx.query(aggregateSql)DuckDB executes the portable SQL emitted for the local profile. The live profile
executes the corresponding Databricks SQL against a warehouse. Keep analytical SQL in a single emitter
(the repo's pattern is a shared SQL AST in packages/warehouse with
emitDatabricksAggregate / emitDuckDbAggregate) so both engines stay in sync
by construction.
Client-boundary mocks
Everything that talks to the Databricks REST API goes through a
DatabricksRestClient that accepts an injected fetch/client. Unit tests stub at
that boundary and drive multi-poll state machines with sequenced responses:
import { createMockDatabricksClient } from '@fabricorg/databricks-testkit'
const client = createMockDatabricksClient()
client.get
.mockResolvedValueOnce({ state: { life_cycle_state: 'RUNNING' } })
.mockResolvedValueOnce({
state: { life_cycle_state: 'TERMINATED', result_state: 'SUCCESS' },
})
const run = await jobs.wait(runId, { client })This tests job/pipeline orchestration logic—retries, timeouts and terminal-state
handling—in milliseconds. Both the REST client and OAuth exchange accept an
injectable fetchImpl, so deterministic response sequences remain ordinary
committed test fixtures rather than recordings that can contain credentials.
Fixtures
Fixtures are declarative and engine-agnostic — the same seed applies to a local DuckDB table or a scratch Unity Catalog schema in live runs:
await ctx.loadFixture({
table: 'conversions',
schema: 'subject_id STRING, event_name STRING, value DOUBLE, at TIMESTAMP',
rows: [
['u1', 'purchase', 49.0, '2026-07-01T10:00:00Z'],
['u2', 'purchase', 12.5, '2026-07-01T11:30:00Z'],
],
})Cross-engine parity
Golden-result suites prove DuckDB and Databricks agree:
import { defineParitySuite } from '@fabricorg/databricks-testkit/parity'
defineParitySuite({
name: 'aggregate-attribution',
sql: emitDatabricksAggregate(query),
fixtures: [
{
table: 'exposures',
schema: 'experiment_id STRING, subject_id STRING, variant_key STRING',
ndjsonFile: 'fixtures/exposures.ndjson',
},
{
table: 'conversions',
schema: 'subject_id STRING, event_name STRING, value DOUBLE',
ndjsonFile: 'fixtures/conversions.ndjson',
},
],
golden: 'golden/aggregate-attribution.json',
})Locally the suite asserts DuckDB output against the golden file. When live
credentials are present (see Live artifact checks) it also
runs the statement on a real warehouse and asserts both engines match the same
golden rows. A change to the golden file is a reviewable, versioned event —
exactly like the frozen assignment vectors in packages/testkit.