Skip to content

Registration and packaging

Test that Airflow can discover your component through the same channel your deployment uses. A clean check_component report proves shape; this page proves that Airflow can find the component.

Production channel Test with Scope
Airflow configuration The matching pytest ini option Session
Airflow plugins folder airflow_plugins_folder Session
Installed distribution metadata The installed package, or airflow_isolated while developing it Isolated process
Temporary registry entry airflow_components One test; not a production-discovery test

Session configuration

Configure components that production selects through airflow.cfg before Airflow's first import:

Option Production setting
airflow_executor [core] executor
airflow_xcom_backend [core] xcom_backend
airflow_secrets_backend [secrets] backend
airflow_secrets_backend_kwargs [secrets] backend_kwargs
[pytest]
airflow_executor = tests.support.executors.FakeExecutor
airflow_xcom_backend = tests.support.xcom.RecordingXCom

These settings must precede Airflow's first import. XCom backends are especially strict: the Task SDK resolves one when its module loads. The INI reference defines every option.

For file-based AirflowPlugin discovery, point airflow_plugins_folder at the same directory layout your deployment receives:

[pytest]
airflow_plugins_folder = plugins

A relative path resolves from pytest's root. Each source entry is symlinked into the isolated AIRFLOW_HOME/plugins, so edits remain live. A missing or non-directory path stops the session with a usage error.

Runtime component registration

Use airflow_components when the test needs a component registered temporarily, not when the claim is that production will discover it:

def test_my_listener_fires(airflow_components, dag_maker):
    airflow_components.listener(MyListener)
    with dag_maker():
        ...

    dag_maker.run()

    assert MyListener.calls

It registers plugins, listeners, policies, secrets backends, executors, timetables, and priority-weight strategies. Use the named method when scope matters; round_trip() detects an unambiguous bare component. Timetable state uses serialization_round_trip(); see Timetables.

Every registration runs check_component first. Contract failures raise ComponentContractError; registry failures raise ComponentSandboxError. Executor and timetable classes must be importable at module scope. Airflow 3.1 has no Task SDK listener manager, so a core-only listener must use listener(..., task=False).

It does not install XCom backends, notifiers, or providers. Configure XCom for the session, reference notifiers from Dag code, and test provider metadata in an isolated process.

The fixture is Airflow 3 only. On an uncertified 3.x release it probes the available registry surfaces, warns once, and still restores them after the test.

How the channels compose

Session configuration and plugins-folder discovery form the baseline. airflow_components adds a test overlay, then restores that baseline -- not an empty registry.

airflow_config changes conf.get() but not live registries. If it and airflow_executor both set core.executor, the environment-backed airflow_config value wins. Prefer one.

Configuration cannot select an alias registered later in one test. Pass the alias from airflow_components.executor() to run_dag(..., executor=alias).

Isolated entry-point discovery

Install a real package into the test environment and assert that Airflow's manager finds it. Only this path verifies the entry points in the built package.

Before packaging -- or for mutually exclusive distributions -- use airflow_isolated to create synthetic distribution metadata in a one-shot child:

import pytest


@pytest.mark.airflow_isolated(
    entry_points={"apache_airflow_provider": "provider_info = my_pkg.provider:get_provider_info"},
    name="my-provider",
)
def test_provider_is_discovered():
    from airflow.providers_manager import ProvidersManager

    assert "my-provider" in ProvidersManager().providers

Use it for Airflow groups such as airflow.plugins, apache_airflow_provider, and airflow.policy. It proves that the declaration resolves through the installed Airflow; its synthetic metadata cannot prove that pyproject.toml contains the declaration.

The child inherits the isolated AIRFLOW_HOME, database, and secrets; outcomes and tracebacks replay in the parent. Same-module tests with identical payloads share a child, so a timeout or crash fails the batch. Run them serially: xdist workers reject the marker. See the airflow_isolated reference.

Sandbox edges

  • Sandboxed teardown restores the exact prior registry objects, including stateful secrets backend instances.
  • Plugin-cache resets can reload plugins-folder modules. Compare components by stable names, not class or object identity, across tests.
  • The sandbox may rescan the plugins folder. Prefer session configuration when every test needs the component.
  • Session configuration and plugins-folder discovery work on Airflow 2 and 3. The component sandbox requires Airflow 3. Entry-point tests exercise whichever groups the installed Airflow family supports.