Skip to content

Add Pytest plugin and Django testing connector - #1547

Open
Xowap wants to merge 4 commits into
procrastinate-org:mainfrom
Xowap:django_testing
Open

Add Pytest plugin and Django testing connector#1547
Xowap wants to merge 4 commits into
procrastinate-org:mainfrom
Xowap:django_testing

Conversation

@Xowap

@Xowap Xowap commented May 10, 2026

Copy link
Copy Markdown
Contributor

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 like freezegun, significantly easing the process of testing scheduled and deferred tasks.

Successful PR Checklist:

  • Tests
    • (not applicable?)
  • Documentation
    • (not applicable?)

PR label(s):

Summary by CodeRabbit

  • Documentation
    • Updated Django integration-testing guide with revised connector usage and clearer instructions for worker connection cleanup and ORM-based scheduling assertions.
    • Added a new section covering the DjangoTestingConnector and the pytest plugin fixtures.
  • New Features
    • Added a Django testing connector to run worker logic inside Django test transactions and emit job/abort notifications during tests.
    • Added a pytest plugin with run_procrastinate_jobs and arun_procrastinate_jobs fixtures for sync/async execution.
    • Improved scheduled-job testing with time-freezing support.
  • Tests
    • Added integration and unit tests covering notifications, aborts, and frozen-time scheduling behavior.

Xowap added 2 commits May 10, 2026 13:21
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.
@coderabbitai

coderabbitai Bot commented May 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f8d6d09b-c4e2-4050-a35b-e116334db165

📥 Commits

Reviewing files that changed from the base of the PR and between df8c99e and ca4040e.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • docs/howto/django/tests.md
  • pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/howto/django/tests.md

📝 Walkthrough

Walkthrough

This PR adds Django testing infrastructure for running Procrastinate workers inside test transactions. It introduces simulated notifications, database time overrides compatible with freezegun, automatically discovered synchronous and asynchronous Pytest fixtures, integration tests, and updated Django testing documentation.

Changes

Django Testing Infrastructure

Layer / File(s) Summary
DjangoTestingConnector implementation
procrastinate/contrib/django/testing.py
Adds query interception, in-memory job notifications, frozen database time handling, and JSON argument deserialization for Django test adapters.
SQL time override functions
procrastinate/sql/queries.sql
Adds temporary PostgreSQL time functions returning a fixed timestamp for deterministic scheduling tests.
Pytest fixtures and discovery
procrastinate/pytest_plugin.py, pyproject.toml
Adds synchronous and asynchronous inline-worker fixtures and registers the plugin through the pytest11 entry point; adds freezegun to test dependencies.
Connector and fixture validation
tests/integration/contrib/django/*, tests/unit/test_pytest_plugin.py
Tests notifications, frozen database time, sync/async job execution, scheduled-job changes, and optional dependency behavior.
Django testing documentation
docs/howto/django/tests.md
Documents the connector, fixtures, time-travel testing, worker connection cleanup, and manual configuration.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: medihack

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main additions: a Pytest plugin and a Django testing connector.
Linked Issues check ✅ Passed The PR implements the requested Django testing connector, Pytest fixtures, and time-mocking support, matching issue #1546.
Out of Scope Changes check ✅ Passed The changes stay focused on Django testing, pytest fixtures, time-mocking, docs, and tests, with no clear unrelated additions.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented May 10, 2026

Copy link
Copy Markdown

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  procrastinate
  __init__.py
  app.py
  blueprints.py
  connector.py
  exceptions.py
  job_context.py
  jobs.py
  manager.py
  middleware.py
  periodic.py
  psycopg_connector.py
  retry.py
  schema.py
  sync_psycopg_connector.py
  tasks.py
  types.py
  utils.py
  procrastinate/contrib/django
  db_cleanup.py
  django_connector.py
  exceptions.py
  procrastinate_app.py
  settings.py
  testing.py 1-38, 44, 50, 65, 79, 90, 99-100, 131, 161-162, 166-167, 177
  utils.py
  procrastinate/sql
  __init__.py
Project Total  

The report is truncated to 25 files out of 28. To see the full report, please visit the workflow summary page.

This report was generated by python-coverage-comment-action

@github-actions github-actions Bot added the PR type: feature ⭐️ Contains new features label May 10, 2026
@Xowap
Xowap marked this pull request as ready for review May 15, 2026 09:56
@Xowap
Xowap requested a review from a team as a code owner May 15, 2026 09:56
@Xowap

Xowap commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Marking ready for review as I have been testing this enough in my project to gain confidence in its stability

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
procrastinate/pytest_plugin.py (3)

38-46: 💤 Low value

Clarify "all awaiting jobs" in docstring.

The docstring states the fixture "execute all awaiting Procrastinate jobs", but with wait=False the 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 value

Consider connector reuse within a single test.

The fixture creates a new DjangoTestingConnector() instance on each invocation. This means if a test calls run_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 value

Clarify "all awaiting jobs" in docstring.

Same as the sync fixture: the docstring claims to "execute all awaiting Procrastinate jobs" but wait=False means 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

📥 Commits

Reviewing files that changed from the base of the PR and between cceb33e and df8c99e.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • docs/howto/django/tests.md
  • procrastinate/contrib/django/testing.py
  • procrastinate/pytest_plugin.py
  • procrastinate/sql/queries.sql
  • pyproject.toml
  • tests/integration/contrib/django/test_pytest_plugin.py
  • tests/integration/contrib/django/test_testing.py
  • tests/unit/test_pytest_plugin.py

# Conflicts:
#	pyproject.toml
#	uv.lock
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR type: feature ⭐️ Contains new features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Add Django testing connector and Pytest plugin with time-mocking support

1 participant