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.0.1.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.0.1-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: qx_testing-1.0.1.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.0.1.tar.gz
Algorithm Hash digest
SHA256 5839e5edc57b18d7456be0c4c081b128b737e603c36029a01446de064942d65a
MD5 93cb81847173e60680a3c61defae4cce
BLAKE2b-256 f85586e4a9220f54be8d488bdf5791ff1644898869ded35152ff51be2193d521

See more details on using hashes here.

File details

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

File metadata

  • Download URL: qx_testing-1.0.1-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.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0a2b2e881b6e127a35e97d99784f050852c664d9d5552dcdd02c33dd157b0128
MD5 32115f47ace2ea331bb96f6c1d78e77e
BLAKE2b-256 c9215ffe89f96772b17e96c6f24ad4dcde437f23268f879107c1f5186bb49218

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