SBlueprint で単一レコードを宣言する

Apex Stem ドキュメント
Apex StemApexBlueprintSBlueprintTest Data
ApexBlueprint で結合テストデータを組み立てるためのコア API、SBlueprint の基本 5 メソッド (of / set / template / alias / use) を、「最終状態を宣言する」という考え方とともに解説します。

ApexBlueprint で結合テストデータを組み立てる最初のステップは、SBlueprint単一レコードの設計図 を宣言することです。「Account を作って、Industry に Technology を入れて...」という手続きを書く代わりに、「最終的にどんなレコードがあってほしいか」を 1 つの式として宣言します。

このページでは、SBlueprint を組み立てるための基本 5 メソッド (of / .set / .template / .alias / .use) と、順序だけを宣言する .after を順に解説します。親子関係 (withChildren) や量産 (times) のような複数レコードを扱う API は 親子・量産・参照のパターン で扱います。

of(SObjectType): 起点

SBlueprint の宣言は必ずこの静的メソッドから始まります。これからどの SObject タイプの blueprint を組み立てるかを示すための「最初の一歩」です。

シグネチャ: SBlueprint.of(System.Type recordType)

APEX
SBlueprint accountBp = SBlueprint.of(Account.class);

返ってくる SBlueprint インスタンスに対して、後続のメソッドチェーンで値や関係を積み重ねていきます。

.set(field, value): フィールド値を設定する

最もよく使うメソッド。フィールド名と値を 1 組ずつ宣言します。

シグネチャ: .set(String fieldName, Object value)

APEX
SBlueprint accountBp = SBlueprint.of(Account.class)
    .set('Name', 'Test Account')
    .set('Industry', 'Technology')
    .set('AnnualRevenue', 1000000);

振る舞いの要点

  • 同じフィールドに対して .set() を複数回呼ぶと、最後の呼び出しが勝ちます (last wins)
  • .template(...) で前もって入れた値も .set(...) で上書きできます。「共通設定はテンプレート + 検証対象だけ .set で差分」という運用パターンの土台
  • フィールド名はそのテストで検証対象になる項目だけ書きます。必須項目や RecordTypeId など全テストに共通する値は .template(...) 側に逃がす

「これは結局このテストで何を検証したいのか」が .set() の行を読むだけで分かる状態を目指す、という考え方です。

.template(Map): 共通設定の再利用

事前に定義した値の Map を .template(...) でまとめて適用します。RecordTypeId、必須項目、全テストで使い回したいデフォルト値などをここに集約することで、各テスト本体に書く .set(...) を最小限に絞れます。

シグネチャ: .template(Map<String, Object> templateMap)

ベストプラクティス: 単一の Blueprints.cls に集約する

複数のテストで使い回せるテンプレートは、単一の Blueprints.cls に、SObject ごとのメソッドとして並べるのが定番運用です。命名は {SObject の短縮}Basic() (accBasic() / oppBasic() …)。

APEX
public with sharing class Blueprints {
    /** 法人顧客の基本構成 */
    public static Map<String, Object> accBasic() {
        return new Map<String, Object>{
            'Name' => 'TestAccount',
            'Industry' => 'Technology',
            'AnnualRevenue' => 500000
        };
    }
 
    /** 法人顧客: エンタープライズ向け (年商を桁違いに) */
    public static Map<String, Object> accEnterprise() {
        return new Map<String, Object>{
            'Name' => 'EnterpriseAccount',
            'Industry' => 'Financial Services',
            'AnnualRevenue' => 10000000,
            'NumberOfEmployees' => 1000
        };
    }
 
    /** 商談の基本構成 */
    public static Map<String, Object> oppBasic() {
        return new Map<String, Object>{
            'Name' => 'TestOpp',
            'StageName' => 'Prospecting',
            'CloseDate' => Date.today().addDays(30)
        };
    }
}

なぜ SObject ごとにクラスを分けないのか。 分けると、各クラスが Map を返すだけの薄いクラスになり、ファイルが散らばります。1 クラスに並べておくと、全 SObject の基本構成が 1 ファイルに集まります。org 側で必須項目が追加されて結合テストが一斉に落ちたとき、直す場所が「該当する xxxBasic()Map に 1 行足す」で自明になる、というのが実運用上いちばん効きます。バリエーションは accEnterprise() / oppClosed() のように接頭辞付きで同じクラスに並べます。

各テスト側はテンプレートを取り込み、検証対象の差分だけを .set(...) で上書きします。

APEX
SBlueprint accountBp = SBlueprint.of(Account.class)
    .template(Blueprints.accBasic())
    .set('Name', '○○商事');  // このテストでは Name だけが本筋

RecordTypeId のような「全テストで共通だが書き忘れるとテストが落ちる」値は、必ずテンプレート側に入れておくのが安全です。

.alias(name): 識別子を付ける

blueprint に一意な参照名 (alias) を付けます。alias は次の 2 つの場面で使います。

  • 別の blueprint が .use(alias, ...) で値を参照する
  • SOrchestrator.create() 実行後に getByAlias(alias) で生成済みレコードを取り出す

シグネチャ: .alias(String aliasName)

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

振る舞いの要点

  • alias は同じ SOrchestrator 内で 一意である必要 があります。重複すると Duplicate alias detected で実行時エラー
  • .alias(...) を省略した blueprint には __Account_0_1__ のような自動 alias が付与されますが、これを後で getByAlias で取り出すのは現実的ではありません。取り出す予定があるなら必ず明示的に alias を付ける のが原則です
  • .times(n) と組み合わせて 'con_{#}' のようなプレースホルダ付き alias を作るパターンは 親子・量産・参照のパターン で扱います

.use(alias, fromField, toField): 別 blueprint からの値コピー

このメソッドは ApexBlueprint の中で最も多目的な API です。「別の blueprint が持っている値を、自分のフィールドに引き写す」という 1 行で、リレーションの作成データのコピー の両方を表現します。

シグネチャ: .use(String aliasName, String fromField, String toField)

  • aliasName: 参照したい blueprint の alias (.alias() でセットしたもの)
  • fromField: 参照元の blueprint から読むフィールド (例: 'Id', 'Industry')
  • toField: 自分の blueprint に書き込むフィールド (例: 'AccountId', 'Description')

用途 1: リレーションを作る (Id コピー)

最もよく使うパターン。OpportunityAccount に紐付けます。'parentAccount'別の Account 用 blueprint に .alias('parentAccount') で付けた識別子 を指しています。

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 が先に parentAccount を insert して Id を払い出し、そのあと Opportunity を insert する、という順序解決は完全に自動です。親 Id を変数で持ち回るコードは一行も書きません。

用途 2: データを引き写す (Id 以外)

.use(...) は ID 以外のフィールドにも使えます。「親の Name を子の説明に転記したい」のようなケースで便利です。

APEX
SBlueprint contactBp = SBlueprint.of(Contact.class)
    .set('LastName', 'TestContact')
    .use('parentAccount', 'Name', 'Description');

高度な用法

{#} で量産した複数親の一部だけを子に紐付けたい」「親の親 ({P0} / {P1}) を参照したい」といった応用は 親子・量産・参照のパターン で詳しく扱います。

.after(alias): 順序だけの依存 (v2.0.0+)

.use() は「値を運ぶついでに順序も決まる」メソッドでした。.after() はそこから値のコピーだけを抜いたものです。参照先が先のレイヤーで insert されることだけを保証し、フィールドは一切書き換えません。

シグネチャ: .after(String aliasName)

APEX
SBlueprint.of(Task.class)
    .set('Subject', 'フォロー架電')
    .after('baseOpportunity');   // baseOpportunity より後に insert される

項目としては繋がっていないのに、insert 順序だけは決めたいときに使います。典型的にはトリガーの副作用です。「先に商談が存在していないと、後から入れた活動のロールアップが合わない」のように、順序が結果に効くのにレコード同士に lookup が無い、という状況が実際にあります。

APEX
// ❌ 順序を作るためだけに、使いもしない項目へ Id を流し込む
.use('baseOpportunity', 'Id', 'WhatId')
 
// ✅ 順序だけが要るなら、順序だけを宣言する
.after('baseOpportunity')

{#} などのプレースホルダも .use() と同じように使えます (.after('opp_{#}'))。起点と刻みを変える .after(alias, startAt, interval) のオーバーロードも同じ形です。

📌 .use().after() は内部的に同じ依存グラフの辺として扱われます。違いは「値を運ぶ辺か、運ばない辺か」だけで、順序解決の仕組みは完全に共通です (依存解決の仕組み)。

例: 全部を組み合わせる

これまでの基本 5 メソッドを 1 つのテストに組み合わせると、こうなります。

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();
 
    // 検証
    Opportunity created = [
        SELECT Id, Name, AccountId FROM Opportunity WHERE Name = 'Test Opportunity' LIMIT 1
    ];
    Assert.isNotNull(created.AccountId);
}

ポイント:

  • Account 側は テンプレートまかせ。このテストの本筋は Opportunity の作成なので、Account の中身は揃ってさえいれば何でもよい
  • Opportunity 側は 検証対象になる項目だけ .set(...)。「最終的にこの Opportunity が Account に紐付けて作成される」という意図がそのまま読める
  • SOrchestrator が依存関係を解決してくれるので、親子の insert 順を意識する必要はなし

このページの主題が 単一レコードの宣言 のため、ここでは 2 つの SBlueprint を別々に .add(...) して .use(...) でつなぐ書き方をしています。ただし実際の運用では、親子関係を作るときは withChildren で「Account の下に Opportunity がぶら下がる」構造をそのまま 1 つの blueprint として書く方が、コードのインデント階層がデータ階層と一致して可読性が高い ため、そちらを優先するのが推奨です。詳しくは 親子・量産・参照のパターン で扱います。

関連ドキュメント