ApexBlueprint: Declaring a single record with SBlueprint

Apex Stem Docs
Apex StemApexBlueprintSBlueprintTest Data
Walk through the five core SBlueprint methods (of / set / template / alias / use) used to declare a single record as a blueprint, with end-to-end examples and the {Object}Blueprint template convention.

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)

APEX
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)

APEX
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).

APEX
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 matching xxxBasic() 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.

APEX
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 via getByAlias(alias)

Signature: .alias(String aliasName)

APEX
SBlueprint accountBp = SBlueprint.of(Account.class)
    .template(Blueprints.accBasic())
    .alias('parentAccount');

Behavior Notes

  • Aliases must be unique within the same SOrchestrator. Duplicates raise Duplicate alias detected at runtime
  • Blueprints without an explicit .alias(...) get an auto-alias like __Account_0_1__, but retrieving these via getByAlias later 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.

APEX
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".

APEX
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)

APEX
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.

APEX
// ❌ 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:

APEX
@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 Account side 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 Opportunity side 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
  • SOrchestrator resolves 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, withChildren lets 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.