API リファレンス: IEntry / Entry / MockEntry

Apex Stem ドキュメント
Apex StemApexEloquentAPI ReferenceIEntryMockEntrySalesforceApex
IEntry インターフェースとその実装 Entry / MockEntry の詳細リファレンス。フィールド・リレーション操作とテスト用の拡張 API (set / template / setParent / setChildren / autoId / alias / asAggregateResult ほか) を網羅します。

IEntry は ApexEloquent のレコードラッパー (インターフェース)。Eloquent.get(scribe) などの戻り値が List<IEntry> で返ります。本番は Entry、テストは MockEntry を使います。

使い方や典型シナリオは データ取得と DML、IEntry、Mock親項目・子サブクエリ・多対多 を参照してください。

3 つの関係

CODE
IEntry  (interface)        ← Usecase / ビジネスロジックが扱う型
   ↑
   ├── Entry               ← 本番。SObject や AggregateResult を内包
   └── MockEntry           ← テスト。SObject 型に縛られず、自由にフィールドを set できる

IEntrySObject ラッパーであり、SObject では作れないテストデータ (数式項目・ロールアップ・親リレーション・auto-number への書き込み) を MockEntry.set 経由で表現できます。また AggregateResult も同じ IEntry インターフェースで扱えるので、SObject クエリと集計クエリの取り出し側コードを揃えられます。

IEntry (インターフェース)

フィールドアクセス

シグネチャ戻り値用途
get(String fieldName)Object任意フィールドの値取得 (キャスト必須)
put(String fieldName, Object value)void値の書き込み
getId()IdId 専用 getter (キャスト不要)
getName()StringName 専用 getter (キャスト不要)
getRecord()SObject内包する SObject を取り出す (最終手段)
setRecord(SObject record)IEntry内部用。SObject 差し替え
setFieldStructure(FieldStructure fs)IEntry内部用。SELECT 漏れ検知のスキーマ設定
setDescribeResult(Schema.DescribeSObjectResult)void内部用
APEX
IEntry oppEntry = eloquent.first(oppScribe);
Id oppId = oppEntry.getId();
String name = oppEntry.getName();
String industry = (String) oppEntry.get('Industry__c');
oppEntry.put('Status__c', 'Active');

リレーション (読み取り)

シグネチャ戻り値用途
getParent(String parentIdFieldName)IEntry親レコードを取得 (例: 'AccountId')
getChildren(String name)List<IEntry>子レコードのリスト
getThrough(String junctionName, String relatedKey)List<IEntry>多対多。Junction オブジェクト経由で関連先を取得

getChildren / getThrough の第 1 引数は、オブジェクト名と relationship 名のどちらでも解決されます。まずオブジェクト名として探し、見つからなければ relationship 名として解決します。

APEX
// どちらでも同じ結果になる
List<IEntry> opps = accountEntry.getChildren('Opportunity');    // オブジェクト名
List<IEntry> opps = accountEntry.getChildren('Opportunities');  // relationship 名

Scribe 側で relationName(...) を明示した場合は、その名前でも引けます。

非推奨: *ByRelationName

シグネチャ代替
getChildrenByRelationName(String childRelationName)getChildren(String)
getThroughByRelationName(String junctionRelationName, String relatedKey)getThrough(String, String)

上記の名前解決が統合されたことで、relationship 名専用の入口は不要になりました。後方互換のため残っていますが、新規コードでは使わないでください。将来のバージョンで削除される可能性があります。

Entry (Production)

IEntry を実装する本番クラス。SObject 由来と AggregateResult 由来の両モードをサポートし、Scribe の選択フィールドに基づいた SELECT 漏れ検知を内部で行います。追加の public メソッドは無し

Eloquent.get(scribe) の戻り値の各要素は Entry インスタンスです。

バッチ状態に載せられます (v3.4.1+)

IEntryDatabase.Stateful のバッチのインスタンス変数として持ち回れます。

⚠️ v3.4.0 以前は、チャンクをまたぐ時点で SerializationException になることがありました。 内部に持つ Schema.DescribeSObjectResult がシリアライズ不可のためです。しかもキャッシュが温まっているかどうかで発生が変わるため (その型で最初にキャッシュミスしたインスタンスだけが値を持つ)、同じコードが通ったり落ちたりしました。v3.4.1 で該当フィールドを transient にし、必要なときに都度導出する形に変えて解消しています。

MockEntry (Mock 拡張)

テスト用の IEntry 実装。IEntry の契約に加え、テストデータ構築用の拡張 API を多数持ちます。

ファクトリ

シグネチャ用途
MockEntry.of(System.Type recordType)通常の SObject 型でエントリを作る (MockEntry.of(Account.class))
MockEntry.asAggregateResult()集計クエリ結果用のエントリを作る (SObject 型に紐づかない)
MockEntry.asAggregateResult(Map<String, Object> fieldToValue)同上、初期値付き

フィールド操作

シグネチャ戻り値用途
set(String fieldName, Object value)MockEntryフィールドへの値設定 (書き込み不可項目も OK)
template(Map<String, Object> fieldToValue)MockEntry複数フィールドを Map で一括セット
APEX
MockEntry accEntry = MockEntry.of(Account.class)
  .template(new Map<String, Object>{
    'Name' => 'Acme Co.',
    'Industry' => 'Technology'
  })
  .set('Active__c', true);

SObject フィールド名 typo の即時検知: set / add / setParent / addParent存在しない SObject フィールド名 を渡すと、即座に ApexEloquentException が投げられます。

APEX
MockEntry.of(Account.class).set('Naame', 'foo');
// → ApexEloquentException ("The field 'Naame' does not exist on the SObject.")

これまでは typo は fieldToValue に黙って入り、SUT が正しいフィールド名 (get('Name')) で取りに来た時に null が返って、テストは通るが本番で落ちる、という偽陽性の温床でした。今は テストセットアップの瞬間に叩き出される ので、セットアップ起源の偽陽性が構造的に塞がれます。

なお put は標準 SObject.put 経由でフィールド名検証が走るので、元から typo は弾かれており、挙動変更はありません。

set には add(String, Object) という同義のメソッドもあります。挙動は同じ (どちらも新しい MockEntry を返す) なので、新規コードは set に寄せてください。setParent / setChildren にも同様に addParent / addChildren があります。

SELECT 契約を焼き付ける: fetchedBy

シグネチャ戻り値用途
fetchedBy(Scribe scribe)MockEntryその Scribe の SELECT 句を、このエントリの契約として焼き付ける

MockEloquent 経由で返すエントリは、get(scribe) に渡した Scribe から契約が自動的に付きます。SELECT していない項目に触れば例外になる、あの仕組みです。

一方、エントリを直接 SUT に渡す場合は契約がありませんScribe を通っていないので、どの項目が SELECT 済みかを知りようがないからです。fetchedBy は、そこに契約を後付けします。

APEX
MockEntry card = MockEntry.of(BusinessCard__c.class)
  .autoId(1)
  .set('CompanyName__c', 'Acme')
  .fetchedBy(RematchCompanyCardsHandler.scope());

これが効くのは、主に バッチです。execute(bc, scope) に渡ってくるレコードは IEloquent を通らないため、本番では実クエリの結果としてプラットフォームが検査してくれますが、テストでは自前で組んだエントリなので何も検査されません。クエリの組み立てを @TestVisible なメソッドに切り出しておけば、本番とまったく同じ Scribe をテストから渡せます。

詳しくは API リファレンス: Scribe の「Scribe を切り出すと、テストでも SELECT 漏れ検知が効く」を参照してください。

本番の EntryfetchedBy は要りません。実 SOQL の結果は、未 SELECT の項目に触れた時点でプラットフォーム自身が例外を投げるためです。これはモック側だけに必要な厳密化の道具です。

リレーション (組み立て)

シグネチャ戻り値用途
setParent(String parentIdFieldName, MockEntry parent)MockEntry親レコードをぶら下げる
setChildren(String name, List<MockEntry> children)MockEntry子レコードリストをぶら下げる

setChildren の第 1 引数は、Scribe 側で relationName を指定したならその名前、指定していないなら オブジェクト名 ('Opportunity' のように単数、__r なし)。Scribe と MockEntry で同じキーを使う必要があります。

ID 自動生成

シグネチャ戻り値用途
autoId(Integer suffix)MockEntry18 桁の Id を自動生成 (数値サフィックス)
autoId(String suffix)MockEntry18 桁の Id を自動生成 (文字列サフィックス、{#} などのプレースホルダ展開対応)

量産 (times)

シグネチャ戻り値用途
times(Integer count)List<MockEntry>テンプレートから count 件展開。プレースホルダ {#} {A} {a} を連番に置換
times(Integer count, Integer startAt)List<MockEntry>連番の開始番号を指定
times(Integer count, Integer startAt, Integer interval)List<MockEntry>連番の開始番号と increment を指定
APEX
List<MockEntry> contacts = MockEntry.of(Contact.class)
  .autoId('{#}')
  .set('LastName', 'Contact-{#}')
  .alias('con_{#}')
  .times(3);  // con_1, con_2, con_3

ネストした掛け算には未対応: MockEntry.of(Account.class).times(2) の中で子側に times(4) を持たせて「親 2 件 × 子 4 件ずつ」のように 階層的に掛け算で展開する ことはできません。子側を個別に setChildren 内で列挙する形で組み立ててください。

alias (生成 Id の取り出し)

シグネチャ戻り値用途
alias(String name)MockEntryこのエントリに名前を付ける
getByAlias(String name)MockEntry再帰検索で alias から MockEntry を取得
getAliasId(String name)Idalias の自動生成 Id を取得 (アサーション用に便利)
APEX
MockEntry oppEntry = MockEntry.of(Opportunity.class)
  .alias('opp').autoId(1);
Id oppId = oppEntry.getAliasId('opp');
 
// テストで:
Opportunity updated = (Opportunity) mock.upsertedRecordsAt(MyUsecase.LBL_UPDATE)[0];
Assert.areEqual(oppId, updated.Id);

検知の無効化 (2 系統)

MockEntry の検知には 2 系統 あり、それぞれ独立した escape hatch を持ちます。どちらも原則使いません (検知が偽陽性を塞いでいるため)。

シグネチャ戻り値無効化するもの
withoutFieldValidation()MockEntryScribe FieldStructure チェック (Scribe で SELECT されていないフィールドへの get(...) アクセスを許可)
withoutSObjectFieldValidation()MockEntrySObject フィールド名 チェック (set / add / setParent / addParent で実在しないフィールド名を許可)
APEX
// 例: 合成フィールド名でデータを保持したいレアケース
MockEntry.of(Account.class)
  .withoutSObjectFieldValidation()
  .set('Synthetic__c', 'value');  // Account には存在しないが許可される

この 2 つは別フラグです。withoutFieldValidation() で Scribe チェックを緩めても、SObject typo 検知は閉じたまま (= set('Naame', ...) のような明らかな typo は依然として叩き出される)。逆も同様。各 escape hatch は自分の責務だけを無効化し、もう片方の安全網は維持されます。

次に読む