FabricFabricExperiments
Testing on Databricks

Local testing

Hermetic Databricks tests with no workspace — DuckDB execution contexts, client-boundary mocks, fixtures, and cross-engine parity.

Status: Available (Phase 1 shipped) — @fabricorg/databricks-testkit provides the DuckDB context, fixtures, golden files, the mock client, and cross-engine parity suites. The live half of parity runs when DBX_TEST_LIVE=1 is set (Phase 3 wires it into CI).

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, terminal-state handling — in milliseconds. Both the REST client and OAuth exchange accept an injectable fetchImpl; a record/replay fixture layer is planned for the live tier.

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'

defineParitySuite({
  name: 'aggregate-attribution',
  sql: emitDatabricksAggregate(query),
  fixtures: ['fixtures/exposures.ndjson', '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.

On this page