Skip to content

TestOutboxBroker dispatches publishes from rolled-back transactions #218

Closed as not planned

Description

@lesnik512

TestOutboxBroker ignores the caller's transaction. Every fake publish path, OutboxResponse included, stores and dispatches the row immediately, so a publish inside a transaction that later rolls back still runs the downstream handler. A test can't check "rollback means no message", and a chained row runs inside the parent handler before the parent acks.

One way: when the session is a real AsyncSession, hook after_commit and after_soft_rollback on its sync_session and defer the store and dispatch until commit. Keep immediate dispatch for mock sessions. This changes when handlers run in sync mode, and existing user tests may depend on the current timing.

Until then, docs/usage/testing.md should state the limitation and point to the integration tests.

Activity

  1. added
    enhancementNew feature or request
    needs-triageMaintainer needs to evaluate this issue
    bugSomething isn't working
    and removed
    enhancementNew feature or request
    on Oct 17, 2026
  2. lesnik512 commented on Oct 17, 2026

    @lesnik512
    MemberAuthor

    This was generated by AI during triage.

    Closing as won't fix. The behaviour is now documented, and making the fake transaction-aware costs more than it buys.

    Confirmed: broker.publish / publisher.publish inside a transaction that rolls back still run their handlers under TestOutboxBroker, because the fake store ignores the session.

    Why not fix it:

    • Few tests would benefit. Transaction awareness only matters for tests that pass a real session to TestOutboxBroker. Since 0.16.0 (feat!: TestOutboxBroker publishes through the real producer #238) the documented setup is fake_session(), a mock with no real transaction.

    • The handler has to run before commit() returns. That's what sync mode promises: assert straight after the commit. Doing it from an after_commit hook needs SQLAlchemy's semi-private sqlalchemy.util.await_only. I verified that it works:

      • on SQLAlchemy 2.0.0, 2.0.54 and 2.1.5, under every join_transaction_mode;
      • with begin and with autobegin;
      • with hook exceptions propagating to the commit() caller.

      2.1 renames it await_ and keeps await_only only as a deprecated alias.

    • Savepoints add more work. A savepoint rollback (after_soft_rollback with nested) and a close without commit (after_transaction_end only) each need their own handling.

    What's documented instead: docs/usage/testing.md (0.16.0) says the fake store ignores transactions, so a row published in a transaction that rolls back is still stored and delivered, and that rollback behaviour should be tested against a real database.

    If this comes back: after #236 the change is confined to FakeOutboxClient.insert. It would buffer rows per transaction, deliver them on commit, and drop them on rollback or close, savepoints included.

    Prior art:

    • Django's captureOnCommitCallbacks(execute=True) and Rails 5+'s after_commit in transactional tests both run commit callbacks in tests.
    • FastStream's own test brokers have no transaction concept.
  3. added
    wontfixThis will not be worked on
    and removed
    needs-triageMaintainer needs to evaluate this issue
    on Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingwontfixThis will not be worked on

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions