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 できる

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

IEntry (インターフェース)

フィールドアクセス

シグネチャ戻り値用途
get(String fieldName)Object任意フィールドの値取得 (キャスト必須)
put(String fieldName, Object value)void値の書き込み
getId()IdId 専用 getter (キャスト不要)。実 SOQL と同じく、Scribe で field('Id') を宣言していなくても読める (v3.8.0+ / v2.6.0+。集計由来のエントリは null)
getName()StringName 専用 getter (キャスト不要)
getRecord()SObject内包する SObject を取り出す (最終手段)。返るのは防御コピーなので、書き換えても Entry には反映されない (更新は put() で行う)。集計由来のエントリでは例外
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+)

IEntry を Database.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 を返す) ですが、add は @deprecated なので、新規コードは set を使ってください。setParent / setChildren に対する addParent / addChildren も同様に @deprecated です。

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 漏れ検知が効く」を参照してください。

本番の Entry に fetchedBy は要りません。実 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 は自分の責務だけを無効化し、もう片方の安全網は維持されます。

次に読む