Skip to content
GitHubDiscord

Testing Strategies

Lexigram is designed with testability at its core. By using the lexigram-testing package, you can write tests that are completely decoupled from your infrastructure (DB, Cache, Queues) and run entirely in-process.


In Lexigram, we prefer Fakes (real, in-memory implementations of protocols) over Mocks (magic objects that record calls). Fakes provide a more realistic environment while maintaining the speed of unit tests.

ToolPurposeWhen to Use
Unit TestsLogic testingUse Fakes for all dependencies.
Integration TestsInfrastructure verificationUse real containers (Redis, PG).
E2E TestsSystem-wide verificationUse the TestClient to hit your API.

Lexigram’s pytest plugin automatically provides fixtures for all core services.

import pytest
from lexigram.testing.clients.cache import FakeCacheBackend
from lexigram.testing.clients.events import FakeEventBus
@pytest.mark.asyncio
async def test_orders_are_cached(fake_cache: FakeCacheBackend, service: OrderService):
# Perform action
await service.place_order(id="123")
# Assert using the Fake's state
cached = await fake_cache.get("order:123")
assert cached is not None

Every Fake implementation in lexigram-testing includes helper methods to make assertions about what happened during the test.

async def test_emit_event(fake_event_bus: FakeEventBus, service: OrderService):
await service.place_order(id="123")
# helper for clean assertions
assert fake_event_bus.published_count("OrderPlaced") == 1
event = fake_event_bus.last_published("OrderPlaced")
assert event.order_id == "123"
async def test_enqueue_task(fake_queue: FakeTaskQueue, service: UserService):
await service.register(email="admin@lexigram.dev")
assert fake_queue.enqueued_count("send_welcome_email") == 1
job = fake_queue.last_enqueued("send_welcome_email")
assert job.kwargs["email"] == "admin@lexigram.dev"

Use the LexigramTestClient to test your controllers without starting a real HTTP server.

from lexigram.testing import LexigramTestClient
async def test_get_profile(client: LexigramTestClient):
response = await client.get("/api/profile", headers={"X-Tenant-ID": "lexigram"})
assert response.status_code == 200
assert response.json()["user"] == "geezmo"

For tasks that depend on the current time (e.g., expiry logic), Lexigram provides a TimeService that can be frozen or advanced in tests.

from lexigram.testing.time import freeze_time
@freeze_time("2024-01-01 12:00:00")
async def test_token_expiry(service: TokenService):
token = await service.create_token()
assert token.expires_at == "2024-01-01 13:00:00" # +1 hour

[!TIP] Use the @pytest.mark.integration marker to separate fast unit tests from slower infrastructure-dependent tests. You can then run them selectively: uv run pytest -m "not integration"


  • FakeDatabase: In-memory SQL/NoSQL provider.
  • FakeCacheBackend: In-memory dictionary-based cache.
  • FakeEventBus: In-process pub/sub tracker.
  • FakeTaskQueue: Background job tracker.
  • FakeAIClient: Stubbed LLM that returns pre-configured responses.