Add Pytest plugin and Django testing connector#1547
Conversation
This introduces `DjangoTestingConnector` to provide a unified testing environment for Django applications without database side-effects or test contamination. It simulates PostgreSQL LISTEN/NOTIFY in-memory for `defer_jobs`, `defer_periodic_job`, and `cancel_job`, avoiding the need for a real worker during tests. Additionally, it integrates automatically with `freezegun` by using a custom schema `_procrastinate_testing` to override PostgreSQL system time functions (`now()`, `transaction_timestamp()`, etc.) directly on the connection level. This ensures both application logic and database queries share a consistent view of time during tests. Comprehensive tests are included to verify both the notification simulation and time freezing capabilities, and `freezegun` has been added to the test dependencies.
Introduces a pytest plugin for Procrastinate that automatically registers if `pytest` and `django` are available. The plugin provides `run_procrastinate_jobs` and `arun_procrastinate_jobs` fixtures, allowing users to easily execute background jobs within tests using the `DjangoTestingConnector`. This ensures environments without pytest or django are unaffected while simplifying job processing in tests. The `DjangoTestingConnector._dictfetch` was also updated to properly parse JSONB arguments, which avoids crashes when accessing job properties like `job.task_kwargs.items()` within a Django test context. Finally, the testing documentation has been updated to showcase how to use the plugin with tools like `freezegun` and Django's ORM.
📝 WalkthroughWalkthroughThis PR introduces a complete testing infrastructure for Procrastinate in Django projects. A new ChangesDjango Testing Connector and Pytest Plugin
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
for more information, see https://pre-commit.ci
Coverage reportClick to see where and how coverage changed
The report is truncated to 25 files out of 27. To see the full report, please visit the workflow summary page. This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Marking ready for review as I have been testing this enough in my project to gain confidence in its stability |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
procrastinate/pytest_plugin.py (3)
38-46: 💤 Low valueClarify "all awaiting jobs" in docstring.
The docstring states the fixture "execute all awaiting Procrastinate jobs", but with
wait=Falsethe worker processes jobs until none are immediately available. If jobs spawn other jobs or have complex dependencies, users may need to call the fixture multiple times to process everything.Consider clarifying: "Runs the Procrastinate worker once to process available jobs."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@procrastinate/pytest_plugin.py` around lines 38 - 46, Update the docstring for the fixture in procrastinate/pytest_plugin.py to clarify that it runs the worker only once and only processes jobs that are immediately available (i.e., uses wait=False), so jobs that spawn additional jobs or have complex dependencies may require calling the fixture multiple times; replace "execute all awaiting Procrastinate jobs" with a precise sentence such as "Runs the Procrastinate worker once to process available jobs (uses wait=False); jobs that spawn further work may require repeated calls." Reference the fixture's docstring block to make this edit.
49-54: 💤 Low valueConsider connector reuse within a single test.
The fixture creates a new
DjangoTestingConnector()instance on each invocation. This means if a test callsrun_procrastinate_jobs()multiple times, each call gets a fresh connector with no shared notification state.If this is intentional for strong test isolation, consider documenting this behavior. If tests should share connector state across multiple calls, consider creating the connector once per test by moving it outside the inner function
f.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@procrastinate/pytest_plugin.py` around lines 49 - 54, The current inner function f creates a new DjangoTestingConnector() each call so repeated calls in the same test don't share notification state; to reuse connector state per test, instantiate DjangoTestingConnector() once outside f (e.g., in the enclosing fixture scope) and reuse that instance inside f when calling django_app.replace_connector(...)/django_app.run_worker(...); alternatively, if isolation is intended, add a short inline comment or update the surrounding fixture docstring to state that f creates a fresh connector on every invocation. Ensure you reference the DjangoTestingConnector and the f wrapper that calls django_app.replace_connector and django_app.run_worker when making the change.
60-66: 💤 Low valueClarify "all awaiting jobs" in docstring.
Same as the sync fixture: the docstring claims to "execute all awaiting Procrastinate jobs" but
wait=Falsemeans the worker runs once. Consider rephrasing for precision.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@procrastinate/pytest_plugin.py` around lines 60 - 66, The docstring for the async fixture (the one described as "Fixture that provides an asynchronous function to execute all awaiting Procrastinate jobs") is misleading because the worker is invoked with wait=False and thus runs once rather than continuously; update the fixture's docstring (and mention run_procrastinate_jobs for parity) to state that the returned async helper runs the worker a single time to process jobs currently queued (replacing the app connector with DjangoTestingConnector for the duration) and does not wait for or process jobs that arrive afterward.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@procrastinate/pytest_plugin.py`:
- Around line 38-46: Update the docstring for the fixture in
procrastinate/pytest_plugin.py to clarify that it runs the worker only once and
only processes jobs that are immediately available (i.e., uses wait=False), so
jobs that spawn additional jobs or have complex dependencies may require calling
the fixture multiple times; replace "execute all awaiting Procrastinate jobs"
with a precise sentence such as "Runs the Procrastinate worker once to process
available jobs (uses wait=False); jobs that spawn further work may require
repeated calls." Reference the fixture's docstring block to make this edit.
- Around line 49-54: The current inner function f creates a new
DjangoTestingConnector() each call so repeated calls in the same test don't
share notification state; to reuse connector state per test, instantiate
DjangoTestingConnector() once outside f (e.g., in the enclosing fixture scope)
and reuse that instance inside f when calling
django_app.replace_connector(...)/django_app.run_worker(...); alternatively, if
isolation is intended, add a short inline comment or update the surrounding
fixture docstring to state that f creates a fresh connector on every invocation.
Ensure you reference the DjangoTestingConnector and the f wrapper that calls
django_app.replace_connector and django_app.run_worker when making the change.
- Around line 60-66: The docstring for the async fixture (the one described as
"Fixture that provides an asynchronous function to execute all awaiting
Procrastinate jobs") is misleading because the worker is invoked with wait=False
and thus runs once rather than continuously; update the fixture's docstring (and
mention run_procrastinate_jobs for parity) to state that the returned async
helper runs the worker a single time to process jobs currently queued (replacing
the app connector with DjangoTestingConnector for the duration) and does not
wait for or process jobs that arrive afterward.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 290db28a-c995-4943-8838-137db21f2b94
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (8)
docs/howto/django/tests.mdprocrastinate/contrib/django/testing.pyprocrastinate/pytest_plugin.pyprocrastinate/sql/queries.sqlpyproject.tomltests/integration/contrib/django/test_pytest_plugin.pytests/integration/contrib/django/test_testing.pytests/unit/test_pytest_plugin.py
Closes #1546
As described in #1546, one of the great things about Procrastinate is its robustness when interacting directly with the database. However, when testing applications using the standard in-memory connector, this level of database-level interaction often leads to discrepancies and test failures.
This PR addresses that gap by introducing a new testing connector specifically for Django (
DjangoTestingConnector). Alongside it, a new Pytest plugin automatically equips Django projects with fixtures for running Procrastinate workers in a test environment. Additionally, it offers seamless compatibility with time-mocking solutions likefreezegun, significantly easing the process of testing scheduled and deferred tasks.Successful PR Checklist:
PR label(s):
Summary by CodeRabbit
Documentation
New Features
run_procrastinate_jobs,arun_procrastinate_jobs) for executing queued jobs synchronously and asynchronously in tests.