Born from a legacy org
Apex Stem"We can't write tests"is where this started
The tools didn't come first. They were built so that tests could be written.
One trunk — Handler-Usecase — and the OSS that grew out of it.
The Story
How a five-year-old Salesforce org turned into Apex Stem — one Usecase at a time.
The Challenge
▸A five-year-old Salesforce org, with trigger-on-trigger debt and assertions that didn't really assert.
▸fflib felt too heavy for a small team. Raw Apex felt too unstructured for the long run.
▸I wanted something AI coding assistants could actually follow — a convention small enough to fit in CLAUDE.md.
What emerged
▸Rebuilt incrementally — one Usecase, one mockable query at a time.
▸ApexEloquent came first. ApexBlueprint, ApexTrace, and ApexTools followed as the needs surfaced.
▸The stack you see today is what I'd want if I had to start the refactor over.
The Stack
Four OSS libraries, one architecture. Each can be adopted independently — or together as a unified stack.
ApexEloquent
SOQL/DML ORM with a mock-first testing API. Used for unit-testing the Usecase layer.
SELECT-omission detection via MockEntry
ApexBlueprint
Declarative test data factory. Used for integration-testing the Handler layer with real DML.
Layered topological sort for bulkified DML
GitHub →ApexTrace
Usecase lifecycle logging.Trace.of() with start / skip / finish / log.
TraceFlow-based behavior assertions in tests
GitHub →ApexTools
TriggerHandlerbase class and an HTTP request wrapper with DI support.
Field-change detection helpers for after-update handlers
GitHub →Plus a thin Result DTO convention for the LWC ↔ Apex boundary. Start with one library — start anywhere.
Handler-Usecase Architecture
Two layers. One rule per layer. The rest is up to you.
Handler Layer
Integration tests · ApexBlueprintEntry points — Trigger, Batch, REST, Flow, Schedulable. Absorbs the entry-point ceremony, then hands off to a Usecase.No business logic here.
Usecase Layer
Unit tests · ApexEloquent (mock DB)Business logic. Only invoke() is public. Constructor takes every dependency —no half-built objects.
Laravel-MVC philosophy
Define the trunk only. Reader / Validator / Mapper emerge from the business — they're never enforced.
Tests map 1-to-1
Handler → integration with real DML. Usecase → unit with mock DB. Same convention everywhere.
AI-friendly by design
The whole convention fits in 30 lines of CLAUDE.md. AI codegen doesn't drift — there's nowhere to drift to.
Proof
Saying "it feels like it's working" gets us nowhere, so we doubted it ourselves and measured.
Doesn't mocking make tests worse?
Mock
83.4%
vs
Real DML
79.3%
Measured with mutation testing (planting bugs on purpose and checking whether the tests notice). We planted 334 of them and compared how many the mock version caught against the real-DML version.It didn't drop.
How much faster, exactly?
Mock
15ms
vs
Real DML
272–1,062ms
Median per method. Measured across two orgs in production use.
Won't it slow down as the org grows?
Constant time
The unit layer runs no DML, so only the real-DML side pulls away.
All of these are real measurements, but not large ones (one target class, one run each). Read them as "this is what we measured", not as "this is proven".
Get Started
You don't have to rewrite anything. Start with one query — grow from there.
Take the first step~2 minute read · then continue to the full guide