How do you run TestGen from Dagster?

You run DataOps TestGen from Dagster with an asset check that calls the TestGen REST API after the asset materializes: start a test run, poll the job, read the failed count, and return the result. Set blocking=True and a failed TestGen test stops downstream assets. No connector is needed; Dagster only makes HTTP calls.

Key points

  • DataOps TestGen runs from Dagster through its REST API: POST /api/v1/test-suites/{test_suite_id}/test-runs starts a run, GET /api/v1/jobs/{job_id} polls it, and GET /api/v1/test-runs/{job_id} returns the result counts and score.
  • A Dagster asset check declared with blocking=True stops downstream assets when it fails with ERROR severity, per Dagster's asset check API docs (checked October 2026), so a failed TestGen test can gate the next asset.
  • TestGen tests set to warn severity return a warning status rather than a failure, which maps to Dagster's WARN severity and does not block downstream assets.
  • Dagster asset checks run checks you write; TestGen generates its 34 test types from a profile of 55 column characteristics, so the two fit together rather than compete.
  • DataKitchen documents no DataOps Observability agent for Dagster as of October 2026; TestGen results still reach Observability through TestGen's own export.

Dagster already knows when an asset finished building. DataOps TestGen knows whether the data in it is any good. The join between them is an asset check that calls the TestGen REST API.

TestGen runs from Dagster. There’s no connector, and you don’t need one. This page covers Dagster specifically; the Airflow answer and the CI/CD answer cover the same API calls in those tools.

The three calls

Every call carries Authorization: Bearer <token>, per API Authentication. The endpoints come from Run Profiling and Tests:

StepCallWhat comes back
Start a test runPOST /api/v1/test-suites/{test_suite_id}/test-runs202 Accepted with the job id
PollGET /api/v1/jobs/{job_id}status; stop on completed, error, or canceled
Read the resultGET /api/v1/test-runs/{job_id}result.score and result.result_counts (passed, failed, warning, error)

Submission returns at once. It doesn’t wait for the tests, so your code polls. Give the poll a timeout.

The asset check

import os, time, requests
import dagster as dg

API = os.environ["TESTGEN_API_URL"]  # http://<host>:8530 by default
HDR = {"Authorization": f"Bearer {os.environ['TESTGEN_TOKEN']}"}  # one-hour OAuth access token (or an Enterprise PAT)

def run_testgen_suite(suite_id, timeout_s=1800):
    job_id = requests.post(f"{API}/api/v1/test-suites/{suite_id}/test-runs",
                           headers=HDR, timeout=30).json()["id"]
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        job = requests.get(f"{API}/api/v1/jobs/{job_id}", headers=HDR, timeout=30).json()
        if job["status"] in ("completed", "error", "canceled"):
            break
        time.sleep(15)
    else:
        raise TimeoutError(f"TestGen job {job_id} did not finish")
    if job["status"] != "completed":
        raise RuntimeError(f"TestGen job {job_id}: {job['status']} {job['error_message']}")
    return requests.get(f"{API}/api/v1/test-runs/{job_id}", headers=HDR, timeout=30).json()["result"]

@dg.asset_check(asset="orders_silver", blocking=True)
def orders_silver_testgen():
    result = run_testgen_suite(os.environ["TG_SUITE_ORDERS_SILVER"])
    counts = result["result_counts"]
    blocked = counts["failed"] + counts["error"] > 0
    return dg.AssetCheckResult(
        passed=not (blocked or counts["warning"]),
        severity=dg.AssetCheckSeverity.ERROR if blocked else dg.AssetCheckSeverity.WARN,
        metadata={"score": result["score"], **counts},
    )

The blocking=True part is Dagster’s: a blocking check that fails with ERROR severity stops downstream assets (docs checked October 2026). One gotcha from the same page: if the check emits no result, downstream assets run anyway and Dagster logs a warning. Raising on a TestGen error, as above, keeps a broken call from passing quietly.

What blocks and what doesn’t

TestGen gives each test a severity. A test set to warn returns warning instead of failed, per the testing docs. So the decision stays in TestGen, where the data steward can change it without a deploy. Failed and error results block. Warnings show up in Dagster as a WARN check and the run keeps going.

DataKitchen calls a blocking check like this a tripwire. The April 2026 post built one in Databricks over SSH because the API hadn’t shipped yet. It has now, so the tripwire is three HTTP calls.

Map suites to asset groups

Dagster organizes assets with group_name. Give each group its own TestGen test suite and one blocking check on the group’s last asset. Bronze, silver, and gold groups become three suites, which is the shape DataKitchen’s medallion testing post argues for: test between layers so gold never sees bad silver.

Where results land

In TestGen. The Test Results page holds every result, and the run accepts an optional body with external_url and metadata, which show up under Link and Run details. Put the Dagster run URL there and you can click from a failed test back to the run. The check’s metadata keeps the score and counts in Dagster.

Profiling runs the same way through POST /api/v1/table-groups/{table_group_id}/profiling-runs, but it doesn’t belong in every materialization. TestGen’s scheduling docs recommend profiling every two weeks to a month, so let TestGen’s scheduler run it.

Tokens and Observability

Open source authenticates with an OAuth 2.1 client: trade a stored refresh token for a one-hour access token at the start of each run. Refresh tokens last 30 days, and a new one takes a browser sign-in, so put the renewal on a calendar. Enterprise adds personal access tokens.

DataKitchen documents no DataOps Observability agent for Dagster. TestGen can still export its results to Observability.

Documentation: TestGen API, Run Profiling and Tests, API Authentication, OAuth 2.1 Clients, Connect to Observability

Frequently asked questions

Short answers to the follow-up questions people ask next.

Should I use Dagster asset checks or TestGen?

Use both. Dagster asset checks are the right place for hand-written rules that belong to one asset, such as a key that must never be null. DataOps TestGen profiles 55 column characteristics and generates tests from 34 test types, so it covers the columns nobody wrote a check for. Call TestGen from one blocking asset check per asset group.

Is there a Dagster integration for TestGen?

No, and none is needed. DataOps TestGen exposes a REST API, and a Dagster asset check or op calls it with plain HTTP requests: POST /api/v1/test-suites/{test_suite_id}/test-runs to start a run, GET /api/v1/jobs/{job_id} to poll it, and GET /api/v1/test-runs/{job_id} for the result counts. DataKitchen documents no Dagster package as of October 2026.

How does a failed TestGen test stop a Dagster run?

Declare the asset check with blocking=True and return AssetCheckResult(passed=False) when the TestGen run summary shows failed or error results. Dagster's API docs (checked October 2026) say a blocking check that fails with ERROR severity keeps downstream assets from executing. Return WARN severity for TestGen warnings so they show up without blocking.

Does any data leave my environment when Dagster runs TestGen?

No. DataOps TestGen runs its profiling and tests as read-only SQL inside your database, and TestGen runs on your own infrastructure. The Dagster call carries IDs, counts, and a score, never table rows. TestGen stores computed results plus, for display, the most frequent values and the minimum and maximum text values that profiling records.

Try it on your own data

DataOps TestGen is free and open source. Profile a table and generate its tests this afternoon.