Apex Stem · Test Data Factory
v2.0.1·Release notes

ApexBlueprint

Write the data's final shape, as itself.Dependency resolution and insert ordering are the framework's job.

Relaying parent Ids, bookkeeping insert order, one more factory method per flag — the procedures that follow you around integration-test data prep collapse into a single declaration.

OrderScenario_T.cls — integration test
// The shape of this code is the shape of the dataSOrchestrator orchestrator = SOrchestrator.start()  .add(    SBlueprint.of(Account.class)      .template(Blueprints.accBasic())      .alias('acc')      .withChildren(        SBlueprint.of(Contact.class)          .set('LastName', 'Yamada-{#}')          .times(3))      .withChildren(        SBlueprint.of(Opportunity.class)          .set('StageName', 'Prospecting')));// Zero parent Id relays. Zero insert ordering.

Three Contacts and an Opportunity hang under the Account. The indentation is the hierarchy, andwithChildren wires the parent-child links for you.

Four pains, one design

This isn't a list of four features. It's four procedures you run into while preparing integration-test data, closed off structurally.

The pain

"Hand the parent's Id to the next insert through a variable."

What changes

withChildren anduse turn parent-child links into a declaration, so the bucket brigade of catching an Id after insert and passing it on disappears. Ordering and Id propagation are resolved bySOrchestrator through a topological sort.

Read how SOrchestrator resolves dependencies →
The pain

"createOppFlagA / FlagB / FlagC. One more method per flag."

What changes

Gather the shared fields into template() and override only the delta under test with set(). The march of boolean arguments and the method explosion are gone, and the factory layer stays thin.

Read the template + set division of labour →
The pain

"200 lines of data prep. So what does it actually build?"

What changes

Nesting with withChildren makes the code's indentation the data's hierarchy, and the code that carries parent-child links around in variables disappears. Read it top to bottom andwhat gets built is right there.

Read how withChildren expresses hierarchy →
The pain

"What insert order works here? Sorting dependencies in your head."

What changes

The order of add() is yours to choose; SOrchestrator builds a dependency graph and rearranges it into the correct DML order. Circular dependencies and duplicate aliases are caught at create() and raise an exception.

Read the internals of dependency resolution →
The shape you keep flattening

Test data is a tree.You keep writing it as a list.

Integration-test data is a tree spreading from parents to children, yet a procedural factory flattens it into "a linear sequence of inserts, top to bottom". ApexBlueprint lets you write the tree as a tree: the code's indentation is the hierarchy, withChildren is the branch, and SOrchestrator works out the planting order. Which meansyou no longer translate structure into procedure.

How it reads

The same Account + Contact ×3 + Opportunity: on the left with a procedural TestDataFactory, on the right with ApexBlueprint. The pink lines are the "parent Id wiring" only the procedural side needs; the green lines are the withChildren that erases it.

Manual relay

Procedurally, you catch the inserted parent's Id in a variable and hand it to the child.

Auto-wired

Declaratively, withChildren wires the parent-child link, so the relay code is gone.

The shape

The indentation on the right is the hierarchy of the data it builds (Account > Contact / Opportunity).

TestDataFactory.clsProcedural
Account a = new Account(Name = 'Acme');insert a; List<Contact> cons = new List<Contact>();for (Integer i = 1; i <= 3; i++) {  cons.add(new Contact(    LastName = 'Yamada-' + i,    AccountId = a.Id));}insert cons; Opportunity o = new Opportunity(  Name = 'Renewal',  StageName = 'Prospecting',  AccountId = a.Id);insert o;
OrderScenario_T.clsDeclarative
SOrchestrator orchestrator = SOrchestrator.start()  .add(    SBlueprint.of(Account.class)      .set('Name', 'Acme')      .alias('acc')      .withChildren(        SBlueprint.of(Contact.class)          .set('LastName', 'Yamada-{#}')          .times(3))      .withChildren(        SBlueprint.of(Opportunity.class)          .set('Name', 'Renewal')          .set('StageName', 'Prospecting'))); orchestrator.create();

On an early, simple test this is barely different from a one-line factory call. It starts to pay off once the relationships deepen and the references begin to cross. Even a "diamond, not a tree" dependency likeAccount → Opportunity → Quote → QuoteLineItem plus a sharedProduct2 is declared with nothing butwithChildren and use(relations and bulk generation).

Permission tests need hostile data

Permission tests get postponed because the setup is heavy. Creating a single restricted user already lines up small traps: unique Usernames, mixed DML, locale defaults, resolving a Profile by name. And the part that actually matters comes after that — unless you preparethe data that must not be visible to that user, nothing you assert inside System.runAs means anything when it passes.

Persona

SPersona collapses it into one chain that names a Profile and its permission sets. Usernames are issued as UUIDs so they never collide under parallel execution, and mixed DML is avoided internally.You don't write a UserFactory.

Ownership

owner() moves ownership to another user. Records you created yourself are visible to you, soonly once ownership moves can you test "this should not be visible".

Sharing

sharedWith() declares a manual share as a final state. Assembling the __Share record and inserting it after its parent both disappear behind the declaration.

InvoiceVisibility_T.clsDeclarative
User admin = SPersona.of('admin').profile('System Administrator').create();User rep   = SPersona.of('sales-rep')  .profile('Standard User')  .permissionSets('InvoiceReadOnly')  .create(); SOrchestrator orchestrator = SOrchestrator.start()  .add(    SBlueprint.of(Invoice__c.class).alias('shared')      .owner(admin)      .sharedWith(rep, 'Read'))  .add(    SBlueprint.of(Invoice__c.class).alias('hidden')      .owner(admin));orchestrator.create(); System.runAs(rep) {  // 'shared' reads, 'hidden' stays invisible — that is the expectation}

Whether you declared a share is itself the expected visibility.If a record you never wrote asharedWith for turns up insiderunAs, that is a hole in your sharing configuration, not a flaw in the test. Reading the declarations above the block tells you exactly what this test claims should be invisible.

A self-contradictory declaration stops before it reaches the DML. Sharing an object whose org-wide default is Public, or sharing with the owner itself, raisesApexBlueprintException during the analysis stage ofcreate()(SPersona API reference).

Declare the shape. Delegate the rest.

The API is 11 methods. Nothing here is bolted on; all of it follows from a single design decision — declare the final state, delegate the resolution (a declarative data specification).

Declaration
SBlueprint

The blueprint for one record. Fields, bulk generation, parent-child links, ownership and sharing — all in one chain.

Resolution
SOrchestrator

Rearranges the registered blueprints with a topological sort and resolves their dependencies.

Execution
.create()

Runs the real DML in the correct order, and getByAlias retrieves the created records.

The order of add(), the insert order, the parent Id propagation — SOrchestrator takes on every procedural part. You write only what data you want, and afterwardsgetByAlias pulls records out by alias for your assertions, so no SOQL is needed in the test body either.

Read the thinking behind declarative data specification

Position in Apex Stem

ApexBlueprint is the Test Data Factory among the four OSS libraries that make up Apex Stem. It appears wherever real DML runs in the Handler integration tests of theHandler-Usecase Architecture, taking on the "Handler integration test" side of thetest strategy.

ApexEloquent
Data Access: SOQL / DML and mocking
ApexBlueprint
Test Data Factory: integration-test data for real DML
ApexTrace
Lifecycle Logging: tracing Usecase paths and verifying them in tests
ApexTools
Foundation: the TriggerHandler base class and an HTTP DI wrapper
Related Documents

Start with the Developer Guide

Three steps, followed through in real code: prerequisites and installation, declaring a single record with SBlueprint, then dependency resolution and DML with SOrchestrator.