Summary
SM_BG_TASKS_TASK_ALWAYS_EAGER=true is documented as the way to run Celery tasks inline in tests. In practice, the eager-mode patch only takes effect after host_conftest._patch_celery_eager() runs AND after the FastAPI lifespan fires (the client fixture). Tests that only use the db fixture (no client) never trigger the lifespan, never resolve the global Celery app, and task.delay() tries to hit the real broker.
Reproduction
@pytest.fixture
async def db(): ... # NullPool postgres, no app boot
@pytest.mark.asyncio
async def test_generation_job(db, seed_dataset):
sample = await create_sample(...)
await db.commit()
from lacowiki_sampling.tasks import generate_sample
result = generate_sample.delay(sample_id=sample.id).get(timeout=10)
# ↑ kombu.exceptions.OperationalError: connection refused (broker)
If you add the client fixture, lifespan fires, eager mode kicks in, and the test passes.
Workaround currently used
Every module's tests/conftest.py needs:
def _ensure_celery_eager() -> None:
from background_tasks import celery_app as _ca
from background_tasks.settings import BackgroundTasksSettings
settings = BackgroundTasksSettings()
celery = _ca.build_celery(settings)
celery.conf.task_always_eager = True
celery.conf.task_eager_propagates = True
_ensure_celery_eager()
This eagerly bootstraps the Celery app at conftest import time (before any test fixtures resolve), so task.delay() finds it and respects the eager flag.
Suggested fix
The patch in host_conftest._patch_celery_eager should also call build_celery() once at module load to register the global Celery app. That way module conftests don't need to repeat the bootstrap.
Alternatively, change simple_module_background_tasks so BackgroundTasksSettings.task_always_eager is honoured by build_celery directly (read at instantiation), and the framework calls build_celery() from host_conftest regardless of lifespan firing.
Impact
Every module testing a Celery task without using the FastAPI test client carries this conftest hack.
Environment
simple_module_background_tasks==0.0.3, celery>=5.3
- Discovered Phase 4 (sampling generation_job tests).
Summary
SM_BG_TASKS_TASK_ALWAYS_EAGER=trueis documented as the way to run Celery tasks inline in tests. In practice, the eager-mode patch only takes effect afterhost_conftest._patch_celery_eager()runs AND after the FastAPI lifespan fires (theclientfixture). Tests that only use thedbfixture (noclient) never trigger the lifespan, never resolve the global Celery app, andtask.delay()tries to hit the real broker.Reproduction
If you add the
clientfixture, lifespan fires, eager mode kicks in, and the test passes.Workaround currently used
Every module's tests/conftest.py needs:
This eagerly bootstraps the Celery app at conftest import time (before any test fixtures resolve), so
task.delay()finds it and respects the eager flag.Suggested fix
The patch in
host_conftest._patch_celery_eagershould also callbuild_celery()once at module load to register the global Celery app. That way module conftests don't need to repeat the bootstrap.Alternatively, change
simple_module_background_taskssoBackgroundTasksSettings.task_always_eageris honoured bybuild_celerydirectly (read at instantiation), and the framework callsbuild_celery()fromhost_conftestregardless of lifespan firing.Impact
Every module testing a Celery task without using the FastAPI test client carries this conftest hack.
Environment
simple_module_background_tasks==0.0.3,celery>=5.3