ApexBlueprint: Declaring a single record with SBlueprint
The first step in assembling integration test data with ApexBlueprint is to declare a blueprint for a single record using SBlueprint. Instead of writing the procedural sequence "create an Account, set Industry to Technology, ...", you declare "what records I want to exist in the end" as a single expression.
This page walks through the five core methods of SBlueprint (of / .set / .template / .alias / .use) in order, plus .after, which declares ordering alone. APIs that handle multiple records — relationships (withChildren) and bulk generation (times) — are covered in Relations, Bulk Generation, and Reference Patterns.
of(SObjectType): The Starting Point
Every SBlueprint declaration begins with this static method. It is the "first step" that tells the blueprint which SObject type you are about to assemble.
Signature: SBlueprint.of(System.Type recordType)
SBlueprint accountBp = SBlueprint.of(Account.class);
You then chain subsequent methods on the returned SBlueprint instance to stack values and relationships.
.set(field, value): Setting a Field Value
The method you'll use most often. Declare a field name and value, one pair at a time.
Signature: .set(String fieldName, Object value)
SBlueprint accountBp = SBlueprint.of(Account.class)
.set('Name', 'Test Account')
.set('Industry', 'Technology')
.set('AnnualRevenue', 1000000);
Behavior Notes
- If you call
.set()multiple times for the same field, the last call wins - Values pre-set by
.template(...)can be overridden by.set(...). This forms the basis of the "shared defaults in template, only verification targets in.set" usage pattern - Write only the fields that are the verification target of that specific test. Required fields, RecordTypeId, and other values common to all tests should be pushed into
.template(...)
The idea is to make "what does this test actually verify?" obvious from reading the
.set()lines alone.
.template(Map): Reusing Common Defaults
.template(...) applies a pre-defined Map of values all at once. By consolidating RecordTypeId, required fields, and other defaults you'd like to reuse across all tests, you keep the .set(...) calls in each test body to a minimum.
Signature: .template(Map<String, Object> templateMap)
Best Practice: Consolidate Into a Single Blueprints.cls
For templates reusable across multiple tests, the canonical approach is to put them all in a single Blueprints.cls, as one method per SObject — named {short SObject name}Basic() (accBasic(), oppBasic(), and so on).
public with sharing class Blueprints {
/** Base shape for a corporate account */
public static Map<String, Object> accBasic() {
return new Map<String, Object>{
'Name' => 'TestAccount',
'Industry' => 'Technology',
'AnnualRevenue' => 500000
};
}
/** Account variant: enterprise (an order of magnitude higher revenue) */
public static Map<String, Object> accEnterprise() {
return new Map<String, Object>{
'Name' => 'EnterpriseAccount',
'Industry' => 'Financial Services',
'AnnualRevenue' => 10000000,
'NumberOfEmployees' => 1000
};
}
/** Base shape for an opportunity */
public static Map<String, Object> oppBasic() {
return new Map<String, Object>{
'Name' => 'TestOpp',
'StageName' => 'Prospecting',
'CloseDate' => Date.today().addDays(30)
};
}
}
Why not one class per SObject? Splitting them leaves each class as a thin wrapper that only returns a
Map, scattered across many files. Keeping them together means every base shape lives in one file. When an admin adds a required field on the org and your integration tests all go red at once, the fix is obvious: add one line to the matchingxxxBasic()map. Variants go in the same class with a prefix —accEnterprise(),oppClosed().
Each test then pulls in the template and overrides only the field that is the verification target.
SBlueprint accountBp = SBlueprint.of(Account.class)
.template(Blueprints.accBasic())
.set('Name', 'Acme Trading Co'); // In this test, only Name is the main concern
Values like RecordTypeId that are "common to all tests but cause failures if forgotten" should always live in the template to be safe.
.alias(name): Attaching an Identifier
Attach a unique reference name (alias) to the blueprint. Aliases are used in two situations:
- Another blueprint references this one's value via
.use(alias, ...) - After
SOrchestrator.create()runs, you retrieve the created record viagetByAlias(alias)
Signature: .alias(String aliasName)
SBlueprint accountBp = SBlueprint.of(Account.class)
.template(Blueprints.accBasic())
.alias('parentAccount');
Behavior Notes
- Aliases must be unique within the same
SOrchestrator. Duplicates raiseDuplicate alias detectedat runtime - Blueprints without an explicit
.alias(...)get an auto-alias like__Account_0_1__, but retrieving these viagetByAliaslater is impractical. If you plan to retrieve a record, always attach an explicit alias - Patterns that combine
.times(n)with placeholder aliases like'con_{#}'are covered in Relations, Bulk Generation, and Reference Patterns
.use(alias, fromField, toField): Copying a Value from Another Blueprint
This is the most multi-purpose API in ApexBlueprint. In a single line, "copy a value held by another blueprint into one of my fields" expresses both building a relationship and copying data.
Signature: .use(String aliasName, String fromField, String toField)
aliasName: The alias of the blueprint you want to reference (set via.alias())fromField: The field to read from the source blueprint (e.g.'Id','Industry')toField: The field to write to on this blueprint (e.g.'AccountId','Description')
Use Case 1: Building a Relationship (Id Copy)
The most common pattern. Link an Opportunity to an Account. 'parentAccount' refers to the identifier attached via .alias('parentAccount') on a separate Account blueprint.
SBlueprint oppBp = SBlueprint.of(Opportunity.class)
.set('Name', 'Test Opportunity')
.set('StageName', 'Prospecting')
.set('CloseDate', Date.today().addDays(30))
.use('parentAccount', 'Id', 'AccountId');
SOrchestrator inserts parentAccount first to get its Id, then inserts the Opportunity — the order resolution is fully automatic. You don't write a single line of code to carry the parent Id around in a variable.
Use Case 2: Copying Data (Other than Id)
.use(...) works on fields other than IDs too. It's handy for cases like "I want to copy the parent's Name into the child's description".
SBlueprint contactBp = SBlueprint.of(Contact.class)
.set('LastName', 'TestContact')
.use('parentAccount', 'Name', 'Description');
Advanced Usage
Patterns like "link only some of the parents bulked with {#} to children" and "reference a grandparent ({P0} / {P1})" are covered in detail in Relations, Bulk Generation, and Reference Patterns.
.after(alias): Ordering Without a Value (v2.0.0+)
.use() decides the order as a side effect of carrying a value. .after() is that same method with the value copy taken out. It guarantees only that the referenced blueprint is inserted in an earlier layer, and touches no fields at all.
Signature: .after(String aliasName)
SBlueprint.of(Task.class)
.set('Subject', 'Follow-up call')
.after('baseOpportunity'); // inserted after baseOpportunity
Reach for it when nothing links the records field-wise, but the insert order still matters. Trigger side effects are the usual reason: "the roll-up comes out wrong unless the opportunity already exists when the activity lands" is a real situation in which no lookup connects the two records.
// ❌ Shoving an Id into a field you never read, purely to force an order
.use('baseOpportunity', 'Id', 'WhatId')
// ✅ If order is all you need, declare order
.after('baseOpportunity')
Placeholders such as {#} work exactly as they do in .use() (.after('opp_{#}')), and the .after(alias, startAt, interval) overload mirrors the same sequence controls.
📌
.use()and.after()become edges in the same dependency graph internally. The only difference is whether the edge carries a value; the ordering machinery is identical (How Dependency Resolution Works).
Example: Combining Everything
Combine the five core methods above into a single test, and you get:
@isTest
static void testOppCreation() {
SOrchestrator.start()
.add(
SBlueprint.of(Account.class)
.template(Blueprints.accBasic())
.alias('parentAccount')
)
.add(
SBlueprint.of(Opportunity.class)
.set('Name', 'Test Opportunity')
.set('StageName', 'Prospecting')
.set('CloseDate', Date.today().addDays(30))
.use('parentAccount', 'Id', 'AccountId')
.alias('targetOpp')
)
.create();
// Verification
Opportunity created = [
SELECT Id, Name, AccountId FROM Opportunity WHERE Name = 'Test Opportunity' LIMIT 1
];
Assert.isNotNull(created.AccountId);
}
Points:
- The
Accountside is left to the template. Since this test focuses on creating an Opportunity, the Account's contents just need to be valid — anything works - The
Opportunityside only.set(...)s fields that are verification targets. The intent that "this Opportunity ends up created and linked to an Account" reads directly from the code SOrchestratorresolves the dependencies, so you don't need to think about parent-child insertion order
Since this page focuses on declaring a single record, the example writes two
SBlueprints into separate.add(...)calls and links them with.use(...). In actual usage, however, when you have parent-child relationships,withChildrenlets you write the structure "Opportunity hanging under an Account" as a single blueprint, with the code's indentation hierarchy matching the data hierarchy and improving readability — so that approach is preferred. See Relations, Bulk Generation, and Reference Patterns for details.
Related Documents
- Resolving Dependencies and Executing DML with SOrchestrator: The execution phase that brings the assembled blueprints to life
- Relations, Bulk Generation, and Reference Patterns: Applications of
withChildren/times/{#}/{P0}{P1} - API Reference: SBlueprint: Full method signatures
- Back to the ApexBlueprint Guide