Move from Behave
Keep readable Gherkin while moving Behave suites to Fabric's TypeScript runner and first-class Databricks workload steps.
Fabric keeps the part your stakeholders see—standard Gherkin feature text—and replaces Behave's Python runner layer with cucumber-js, TypeScript support code, and Databricks-aware execution contexts. Python notebooks and pipelines remain valid workloads under test; Python is simply not required to orchestrate them.
Start with the ten-minute BDD quickstart if you want to evaluate the runner before migrating an existing suite.
Decide whether Fabric fits
Fabric is a strong fit when:
- Databricks SQL, Delta, Unity Catalog, Jobs, Lakeflow, Volumes, Lakebase, notebooks, dbt, or Databricks Apps are important test surfaces;
- analysts and product owners should keep authoring readable
.featurefiles; - the platform team is comfortable maintaining reusable steps in TypeScript;
- local DuckDB feedback and live Databricks execution should share scenarios;
- CI evidence, automatic cleanup, least privilege, and resource lifecycle are part of the framework rather than custom glue.
Retain Behave only when Python source compatibility itself is a requirement—for example a third-party plugin imports Behave internals or your organization must continue executing existing Python step modules unchanged. Fabric provides TypeScript equivalents for the user-visible runner capabilities, but it does not pretend that TypeScript classes are Python ABI-compatible.
Capability mapping
| Behave | Fabric |
|---|---|
features/*.feature | Same standard Gherkin files. |
environment.py | Cucumber hooks in TypeScript support files; core hooks ship with Fabric. |
context | Scenario-isolated DatabricksWorld. |
context.add_cleanup() | this.addCleanup() in reverse registration order. |
before_all / after_all | Cucumber BeforeAll / AfterAll. |
| Run-scoped fixture | sharedFixtures.runFixture(key, setup); reverse-order AfterAll cleanup. |
| Feature-scoped fixture | this.featureFixture(key, setup); memoized by feature URI and cleaned in AfterAll. |
before_scenario / after_scenario | Cucumber Before / After. |
before_step / after_step | Cucumber BeforeStep / AfterStep; Fabric attaches failure evidence. |
-D name=value userdata | World parameters or DBX_TEST_USERDATA_NAME. |
| Layered context attributes | setState(scope, key, value) with scenario → feature → run lookup. |
context.execute_steps() | Typed defineAction() helpers and this.runAction(). |
| parse/cfparse types | Cucumber Expressions, custom parameter types, and parseCardinalityField() for ?, *, +. |
@fixture / active tags | Fixture helpers plus @requires.*, @excludes.*, @use.with_*, and @not.with_*. |
--junit | JUnit formatter. |
| rerun formatter | Rerun formatter and manifest. |
| captured stdout/stderr/logging | Secret-redacted, per-step process-output capture. |
--steps-catalog | Generated Fabric steps catalog. |
-w | @wip, fail-fast, summary profile. |
| multiprocessing add-ons | Parallel local scenarios; shared live resources remain serial. |
--stage / configuration variants | Cucumber profiles plus DBX_TEST_STAGE and worldParameters. |
| custom runner / formatter | runFeatures(), Cucumber support-code APIs, and custom formatters. |
The exhaustive behavior-by-behavior status is in the compatibility matrix.
Migrate incrementally
1. Run the existing feature text unchanged
Create a small TypeScript runner project using the BDD quickstart. Copy one feature that uses only Given/When/Then, Background, Scenario Outline, Examples, Rule, and tags.
Import Fabric's standard steps:
import '@fabricorg/databricks-bdd/steps'Run it locally first. Undefined steps are a migration inventory, not a reason to rewrite the Gherkin.
2. Replace generic database glue with shipped steps
Before porting Python implementations, check the step catalog. Fabric already supplies fixture tables, SQL queries, row/golden/schema assertions, permission failures, Jobs, pipelines, Volumes, Lakebase, notebooks, and dbt steps.
For example, replace a custom Python table loader with:
Given a table "orders" with:
| order_id | amount | ordered_at |
| o-1 | 49.0 | 2026-07-01T10:00:00Z |
When I query table "orders"
Then the result has 1 rows3. Port domain steps, not infrastructure steps
Move business language such as “the churn guardrail fires” into TypeScript
steps over DatabricksWorld. Keep SQL emitters and orchestration helpers in
ordinary TypeScript modules so they can also be unit tested. See
Advanced Databricks BDD.
4. Map hooks and cleanup
Fabric already owns context creation, output capture, failure attachment, and
context disposal. Port only product-specific setup. Register temporary
resources with this.addCleanup() instead of relying on a later scenario to
remove them.
For a Behave fixture shared by every scenario in one feature, port the setup
body to this.featureFixture('stable-key', setup). For a run-wide service use
sharedFixtures.runFixture(). Both accept either a value or
{ value, cleanup }, deduplicate concurrent setup, retry failed setup, and
clean up in reverse order. Feature cleanup happens in AfterAll, rather than
Behave's after_feature, because cucumber-js does not expose a safe
after-feature lifecycle event under filtering and parallel execution.
5. Replace Python context composition with typed TypeScript
Port mutable context attributes to explicit layered state and port nested
execute_steps() calls to ordinary typed actions:
import { defineAction, type DatabricksWorld } from '@fabricorg/databricks-bdd'
const createCustomer = defineAction<[string], void>(async (world, customerId) => {
const ctx = await world.context()
await ctx.exec('INSERT INTO customers VALUES (?)', [customerId])
world.setState('scenario', 'customerId', customerId)
})
When('customer {string} signs up', async function (
this: DatabricksWorld,
customerId: string,
) {
await this.runAction(createCustomer, customerId)
})Use run state for suite configuration, feature state for values shared by
scenarios in one feature file, and scenario state for mutable test data.
Scenario values override feature values, which override run values. Pair shared
state with the fixture of the same scope when the value owns a resource.
6. Separate local and live behavior
Keep engine-neutral logic untagged so it runs on DuckDB. Add @live, @jobs,
@dlt, @pipeline, @autoloader, @lakebase, or @slow only when the
scenario genuinely requires that managed surface. The local profile skips those
tags; the live profile runs serially after an eager workspace preflight.
For the equivalent of Behave active tags, describe runtime requirements in Gherkin and provide capabilities through environment variables:
@requires.cloud=azure @requires.compute=serverless
Scenario: serverless contract
Given a Databricks workspace@use.with_cloud=azure and @not.with_cloud=azure are accepted for suites
that want to retain Behave's active-tag spelling.
7. Compare evidence before switching CI
Run Behave and Fabric against the same fixtures for a transition window. Compare scenario counts, expected rows, JUnit results, failure messages, and cleanup. Add Fabric's HTML, rerun, and JSON evidence as CI artifacts. Retire the Python implementation after the same feature behavior is green locally and on the intended Databricks target.
Language boundary, not capability boundary
Fabric does not execute Python step modules or expose Behave's Python object model. It supplies the corresponding TypeScript behaviors: typed actions for step composition, explicit layered state, cardinality parsing, active tags, fixtures, hooks, profiles, formatters, userdata, output capture, and evidence. This keeps compiler checking and warehouse isolation while avoiding two runtime implementations.
featureFixture() cleanup occurs in AfterAll, not immediately after the last
scenario in a feature, because cucumber-js can interleave feature scenarios
under filters, retries, and parallel execution. The resource remains isolated
by feature URI. This lifecycle timing is the remaining observable difference;
it does not require Python or prevent once-per-feature setup.
There is no shipped Python step-definition adapter. It is unnecessary for feature parity in the TypeScript runner and would make Python a production dependency. Tests can still submit Python notebooks, run Python-backed Lakeflow pipelines, and validate PySpark-produced tables.
Evaluation checklist
Before choosing a framework, prove these outcomes with your own workload:
- A representative feature runs locally without Databricks credentials.
- The same business assertion runs on the intended SQL warehouse.
- A failed SQL step includes useful, redacted evidence.
- A temporary table is removed after success and failure.
- A Job or pipeline reaches a terminal state within an explicit timeout.
- A restricted identity is denied access outside the test schema.
- JUnit, HTML, rerun, and JSON evidence are retained by CI.
- Your team can add one domain step in TypeScript.
- The target cloud and compute mode are listed as certified rather than merely API-compatible.
The public published-package consumer is a minimal, cloneable local example. For production design, continue with the advanced playbook.