Skip to content
GitHub

Guide

PackageRequiredPurpose
lexigramYesCore framework
lexigram-contractsYesProtocol definitions
lexigram-sqlYesSQL test utilities

Testing a contract-based async framework means writing test doubles for every protocol — event buses, caches, databases, LLM clients, audit loggers, and more. lexigram-testing provides ready-made fakes, in-memory implementations, compliance test suites, and pre-wired test environments so you focus on testing your logic, not infrastructure.

Mental model: A testing toolkit that mirrors the framework’s architecture. Every protocol has a fake; every fake can be used standalone or wired into the DI container; every real backend can be validated against a compliance suite.


Lightweight in-memory implementations of framework protocols. Usable standalone or wired into the container.

FakeWhat It ReplacesKey Features
FakeCacheRedis, MemcachedTTL expiry, assert_has_key(), assert_value()
FakeEventBusRabbitMQ, KafkaPublish/subscribe, assert_event_raised()
FakeCommandBusCommand dispatchHandler registration, async dispatch
FakeQueryBusQuery dispatchHandler registration, async query
FakeAuditLoggerAudit storeIn-memory log, assert_contains()
FakeUnitOfWorkTransaction managerCommit/rollback tracking
FakeConfigConfigurationDict-backed settings
FakeLoggerstructlogLogEntry capture, assertion helpers
FakeTracerOpenTelemetryFakeSpan capture
FakeMetricsCollectorPrometheusIn-memory metrics
FakeStateStoreState storeKey-value with assert_has_key()
Clock / FakeClock / SystemClockTimeDeterministic time control
from lexigram.testing import FakeCache, FakeEventBus
cache = FakeCache()
await cache.set("key", "value", ttl=60)
cache.assert_has_key("key")
bus = FakeEventBus()
await bus.publish(UserCreatedEvent(user_id="u1"))
bus.assert_published(UserCreatedEvent)

Heavier counterparts to fakes — implement protocols more completely, suitable for compliance testing.

TypeImplements
InMemoryCacheBackendCacheBackendProtocol
InMemoryEventBusEventBusProtocol with wildcard handlers
InMemoryCommandBusCommandBusProtocol
InMemoryQueryBusQueryBusProtocol
InMemoryRepositoryRepositoryProtocol
InMemoryUnitOfWorkUnitOfWorkProtocol
InMemoryAuditLoggerAuditLoggerProtocol
InMemoryDistributedLockDistributedLockProtocol
InMemoryBlobStoreBlobStoreProtocol
InMemoryOutboxOutbox pattern
MemoryProviderDI provider for memory backends

Parameterized pytest test classes that validate a backend correctly implements a protocol. Subclass and implement the factory method:

from lexigram.testing import CacheBackendCompliance
class TestMyCache(CacheBackendCompliance):
async def create_backend(self):
return MyCustomCache()

Available suites: CacheBackendCompliance, EventBusCompliance, DatabaseProviderCompliance, RepositoryCompliance, QueueBackendCompliance, TaskQueueCompliance, VectorStoreCompliance, SearchEngineCompliance, BlobStoreCompliance, DistributedLockCompliance, AuditLoggerCompliance, AuditStoreCompliance, FlagProviderCompliance, MiddlewareCompliance, NotificationChannelCompliance, WebhookDeliveryStoreCompliance, WebhookSubscriptionStoreCompliance.

EnvironmentWhen to Use
TestEnvironmentUnit tests — pre-wired container with in-memory fakes
IntegrationEnvironmentIntegration tests — real providers with optional fakes
AppTestBedFull application tests — boots the real app with override support
from lexigram.testing import TestEnvironment
async def test_service():
env = TestEnvironment()
await env.setup()
service = MyService(
event_bus=env.event_bus,
command_bus=env.command_bus,
)
result = await service.do_something()
assert result.is_ok()
# Inspect published events directly
published = env.event_bus.subscribed_types
assert UserCreatedEvent in published
env.teardown()

Pre-built test clients with test-specific features (budget controls, inspection):

ClientPurpose
WebTestBed / WebTestClientHTTP integration tests with the Lexigram web layer
DatabaseTestBed / DatabaseTestClientDatabase integration tests
AITestBed / AITestClientLLM integration tests with token budget enforcement
TaskTestBed / TaskTestClientBackground task integration tests
AdminTestClientAdmin API testing
from lexigram.testing import AITestClient
client = AITestClient(max_tokens_per_run=1000)
result = await client.complete("Summarize: ...")
from lexigram.testing import (
assert_ok,
assert_err,
assert_result_ok,
assert_result_err,
assert_result_ok_value,
assert_result_err_type,
assert_all_ok,
assert_healthy,
)

These integrate with the Result pattern from lexigram.result:

result = await my_service.find("user-123")
assert_result_ok(result)
user = result.unwrap()
assert user.name == "Alice"

lexigram-testing registers a pytest plugin (lexigram.testing.plugins.pytest) that provides:

  • @requires_redis, @requires_postgres, @requires_rabbitmq — skip-if-unavailable marks
  • @override + @testbed — class-decorator sugar for DI overrides
from lexigram.testing import override, testbed
@testbed("my_app.app:create_app")
class TestAPI:
@override(PaymentGateway, MockPaymentGateway())
async def test_payment(self, bed):
resp = await bed.client.post("/pay", json={"amount": 100})
assert resp.status_code == 200

  • ✅ Use FakeCache, FakeEventBus etc. for unit tests — they are lightweight and assertion-rich
  • ✅ Use TestEnvironment when you need a pre-wired container with multiple fakes
  • ✅ Use compliance suites to validate custom backend implementations
  • ✅ Use assert_result_ok / assert_result_err for Result-returning methods
  • ✅ Use FixedClock to test time-sensitive logic deterministically
  • ❌ Don’t mock protocols — use fakes and memory implementations instead
  • ❌ Don’t test infrastructure behavior — compliance suites already cover that