ApexEloquent
AI writes the code. AI writes the tests.One wall is left, and it's made of physics: running them.
Requirements stay human. Everything after that keeps getting cheaper. But executing a test is bound to DML — to real disk — and no model generation shaves a millisecond off it.ApexEloquent removes that wall structurally. With one injection.
// A unit test that never touches the databaseIEntry opp = MockEntry.of(Opportunity.class) .autoId(1) .set('Amount', 50000000); MockEloquent mock = (new MockEloquent()) .attach('fetch', new List<IEntry>{ opp }); new PriceBandUsecase(mock).invoke(); Assert.areEqual('Large', ((Opportunity) mock .upsertedRecordsAt('update')[0]).PriceBand__c);Feed the mock, call the Usecase exactly as production does, then read back what it wrote. The database never moves. That's why the cost of running tests stops piling up, however many AI writes.
The wall AI can't move
A test creates a record, updates it, and triggers, flows and rollups fire in turn. Those are things you built on the platform. What is actually slow is underneath them: the reads and writes to the database, a physical round trip to disk. No amount of model progress takes a millisecond off that. And here is the awkward part — the closer the cost of writing tests gets to zero, the more the cost of running them stands alone as the one thing setting the pace.
More features, more tests. AI is on the side that accelerates this.
Add one required field or validation rule and every existing test that touches the object is taxed.
RunLocalTests bites as total tests × deploy frequency.
The three multiply, so execution time does not grow linearly — it growssuperlinearly as the org matures. ApexEloquent uses DI to take the dominant term, the database round trip, out of unit tests entirely and run them in memory.This is not "a bit faster". The single largest factor inflating your execution time is gone.
DB-less tests are fast.They're usually blind, too. Not here.
Speed is usually sold together with blindness. Not here. If we're going to claim speed, we should also show that the fast testsstill catch real bugs.
Speed
Run time of the same
test method (median)
DB-less (MockEloquent)
15ms
vs
Real DML
272–1,062ms
Measured across two orgs in production use. The unit layer runs no DML, so as validation rules, related objects and flows pile up,only the real-DML side grows.
Bug-catching
Share of 334 planted
bugs actually caught
DB-less (MockEloquent)
83.4%
vs
Real DML
79.3%
Measured with mutation testing — deliberately planting bugs and checking whether the tests notice. Trading detection away for speeddid not happen.
Both are real measurements, but not large ones (one class, one run each). Read them as "this is what we measured", not as proof.
Even with the numbers in hand, two things tend to stay unresolved. They bothered us too, so they're written out below.
The constraint that catches real bugs — the SELECT clause — is rebuilt inside the mock layer. The blueprint a Scribe declares (fields, relations, aliases) is enforced on the mock, so touching a field you never selected throws in the unit test. False positives die before shipping. The SELECT-omission net →
In a mature org, what "matters" isn't decided by code alone. Every insert and update sets off triggers, flows and rollups, so the blast radius is decided at runtime by code plus metadata plus data. The set of "relevant tests" cannot be enumerated in principle. Narrowing the target doesn't remove risk — ittrades visible slowness for invisible misses, quietly shrinking what "green" means. ApexEloquent goes the other way:run all of them, and make that cheap, so "every class green" keeps meaning "nothing is broken".
Batteries included
Everything you need to write a test lives in one library — the ORM that builds the query, the test data, the execution mock, the spy, the failure injection. Nothing left over to fill in with another library or a helper of your own.
Scribe builds SOQL as a typed chain. SELECT, WHERE and ordering, plus parent fields, child subqueries, many-to-many, aggregates and dynamic conditions — all in the same shape.
MockEntry puts values into fields you cannot normally write — formula, rollup, auto-number. Parent-child graphs and AggregateResult take the same form.
Four read methods, four DML methods and external-ID upsert all land onMockEloquent. Label your call sites and one mock serves them all, kept apart.
Read back which records were saved or deleted, under which label. You get to see what happened without running a single DML.
Throw an exception at exactly one call site. Retry-then-succeed paths and per-record partial save failures are reproducible without touching org configuration.
Touch a field you never selected and the unit test throws. Feed the mock under the wrong key and it throws.Green while verifying nothing is closed off structurally.
On the production side, v3 defaults execution to user mode(respecting the running user's field and object permissions). Only work that must complete no matter who triggers it — aggregation, stamping, migration — opts out explicitly with systemMode().
800+
tests on the library itself
A library for testing is worth little if it is itself broken, so ApexEloquent is held down by over 800 of its own tests. On top of that, it is deployed and running in production across more than ten client projects. It is a library its author uses every working day.
How it reads
The production code and its unit test, side by side. Same IEloquent — real SOQL in production, in-memory in the test. The tinted lines are the seams where the two meet.
The test attaches mock records to 'fetch' — the exact label production reads from.
The test pulls whatever was written to 'update' from the spy, and asserts on it.
eloquent arrives by DI — real in production, mock in the test. The Usecase code itself never changes.
Scribe scribe = Scribe.of(Opportunity.class) .field('Name') .field('Amount') .whereEqual('StageName', 'Prospecting'); List<IEntry> opps = this.eloquent.label('fetch').get(scribe); for (IEntry opp : opps) { Decimal amount = (Decimal) opp.get('Amount'); opp.put('PriceBand__c', amount >= 10000000 ? 'Large' : 'Standard');} this.eloquent.label('update').doUpdate(opps);IEntry opp = MockEntry.of(Opportunity.class) .autoId(1) .set('Name', 'Acme Renewal') .set('Amount', 50000000); MockEloquent mock = (new MockEloquent()) .attach('fetch', new List<IEntry>{ opp }); new PriceBandUsecase(mock).invoke(); List<SObject> updated = mock.upsertedRecordsAt('update');Assert.areEqual('Large', ((Opportunity) updated[0]).PriceBand__c);Why it's one library
A query builder, an execution mock, a test-data factory. Different responsibilities — normally you would split them into separate libraries. There is a reason they are kept together.
SELECT-omission detection only exists if all three points connect.
Scribe builds up what this query selects
Eloquent /MockEloquent hands that declaration back with the result
IEntry checks every access against it and throws on anything not declared
The third one is the key. The check runs at the moment a field is accessed, and the only way to get your own code in there is to own the type the caller touches. Returning IEntry putsget() in the path, and that is where the comparison happens. Return a bare SObject andrecord.Industry becomes a language primitive — not one line of library code runs.It isn't a feature left unbuilt; there is no seam to build it in.
A guarantee reaches only as far as the type you own.
Hand back a plain SObject and it connects to existing code and standard APIs with no conversion, and there is no new type to learn. Hand back a wrapper and you can put a guarantee on the moment of access instead. Neither is better — it is a trade. ApexEloquent takes the second, and keeps declaration through checking in one place to do it.
Deep Dives
Why ApexEloquent ended up in this shape — five documents go deep.
Position in Apex Stem
ApexEloquent is the Data Access piece of Apex Stem. It lives in the Usecase layer of the Handler-Usecase Architecture and carries the "Usecase unit test" half of the test strategy.
- Handler-Usecase Architecture: the responsibility of the Usecase layer where ApexEloquent is called
- Layered Constructor Pattern: the design for DI'ing
IEloquentinto a Usecase - Test Strategy: Usecase unit tests centered on
MockEloquent - Apex Stem Introduction Guide: four steps with working code
Start with the Developer Guide
Three usage paths — building queries with Scribe, fetching data and running DML, and working with relations — all in real code.