Skip to main content

Qx testing helpers: testcontainers, fixtures, mediator/repository doubles, outbox assertions

Project description

qx-testing

Testing helpers for Qx services — testcontainers, mediator and repository doubles, outbox assertions, and pytest fixture factories.

What lives here

  • qx.testing.postgres_container / redis_container / nats_container — context managers that start Docker containers via testcontainers. Each yields the running container with a get_connection_url() or equivalent accessor. Docker Desktop on macOS is detected automatically via ~/.docker/run/docker.sock.
  • qx.testing.MediatorStub — in-memory Mediator replacement. Pre-register handler return values with stub.on(CommandType, result). Asserts that each stubbed command was sent exactly once.
  • qx.testing.RepositoryStub — in-memory Repository[TEntity] backed by a plain dict. Supports add, get, list, save, and soft_delete. No database required.
  • qx.testing.OutboxAssert — queries the real qx_outbox_events table and provides assert_event_published(event_name, where={...}). Used in integration tests to verify that the transactional outbox was populated correctly.
  • qx.testing.container_factory — pytest fixture that builds and tears down a DI Container with all singletons initialized.
  • qx.testing.mediator_factory — pytest fixture that creates a Mediator wired to a test container.
  • qx.testing.http_client_factory — pytest fixture that wraps a FastAPI app in a TestClient with a fresh Prometheus registry per test.

Usage

Integration test with a real Postgres container

import pytest
from qx.testing.containers import postgres_container
from qx.testing.assertions import OutboxAssert

@pytest.fixture(scope="session")
def db_url():
    with postgres_container() as pg:
        yield pg.get_connection_url()

def test_create_user_writes_outbox(client, outbox: OutboxAssert):
    client.post("/v1/users", json={"email": "a@b.com", "name": "Ada"})

    import asyncio
    asyncio.run(
        outbox.assert_event_published(
            "identity.user.registered",
            where={"email": "a@b.com"},
        )
    )

Unit test with doubles

from qx.testing import MediatorStub, RepositoryStub
from qx.core import Result

async def test_create_user_handler():
    users = RepositoryStub(User)
    uow = FakeUnitOfWork(users)
    handler = CreateUserHandler(uow)

    result = await handler.handle(CreateUserCommand(email="a@b.com", name="Ada"))
    assert result.is_success
    assert len(users.all()) == 1

Design rules

  • Use NullPool for all test engines that call asyncio.run() in fixtures — asyncpg connections are bound to the event loop that created them and cannot be reused across asyncio.run() boundaries.
  • RepositoryStub raises the same NotFoundError / ConflictError shapes as the real repository so handler tests exercise error paths without a database.
  • MediatorStub.assert_all_sent() can be called in teardown to catch unexpected commands that were sent but not stubbed.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qx_testing-1.1.0.tar.gz (8.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qx_testing-1.1.0-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

File details

Details for the file qx_testing-1.1.0.tar.gz.

File metadata

  • Download URL: qx_testing-1.1.0.tar.gz
  • Upload date:
  • Size: 8.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for qx_testing-1.1.0.tar.gz
Algorithm Hash digest
SHA256 21bf19d4a7d454240b1be119e7397e8d9170aab43a6fe2c0645d10609cf1893c
MD5 019a04eb107f56e5fcf3e34f09c743ef
BLAKE2b-256 341201c2dddc74d1327b776e81d4abeb3f9f4938047cfd243a1b77b82706bcd0

See more details on using hashes here.

File details

Details for the file qx_testing-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: qx_testing-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 9.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for qx_testing-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 12a1be560fbd2123421606c574f4396d7bbf643babb986ee0ee582739860d4c9
MD5 e0239cc4e104bbff0e1d399a0d9c07c3
BLAKE2b-256 6b343cb1cd8d37039c19e816f3b0d75be9cb995c705b85a108a720b7dfd7bc1b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page