API リファレンス: IEntry / Entry / MockEntry
IEntry は ApexEloquent のレコードラッパー (インターフェース)。Eloquent.get(scribe) などの戻り値が List<IEntry> で返ります。本番は Entry、テストは MockEntry を使います。
使い方や典型シナリオは データ取得と DML、IEntry、Mock と 親項目・子サブクエリ・多対多 を参照してください。
3 つの関係
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() | Id | Id 専用 getter (キャスト不要) |
getName() | String | Name 専用 getter (キャスト不要) |
getRecord() | SObject | 内包する SObject を取り出す (最終手段) |
setRecord(SObject record) | IEntry | 内部用。SObject 差し替え |
setFieldStructure(FieldStructure fs) | IEntry | 内部用。SELECT 漏れ検知のスキーマ設定 |
setDescribeResult(Schema.DescribeSObjectResult) | void | 内部用 |
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 名として解決します。
// どちらでも同じ結果になる
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 で一括セット |
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 が投げられます。
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 は、そこに契約を後付けします。
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) | MockEntry | 18 桁の Id を自動生成 (数値サフィックス) |
autoId(String suffix) | MockEntry | 18 桁の 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 を指定 |
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) | Id | alias の自動生成 Id を取得 (アサーション用に便利) |
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() | MockEntry | Scribe FieldStructure チェック (Scribe で SELECT されていないフィールドへの get(...) アクセスを許可) |
withoutSObjectFieldValidation() | MockEntry | SObject フィールド名 チェック (set / add / setParent / addParent で実在しないフィールド名を許可) |
// 例: 合成フィールド名でデータを保持したいレアケース
MockEntry.of(Account.class)
.withoutSObjectFieldValidation()
.set('Synthetic__c', 'value'); // Account には存在しないが許可される
この 2 つは別フラグです。withoutFieldValidation() で Scribe チェックを緩めても、SObject typo 検知は閉じたまま (= set('Naame', ...) のような明らかな typo は依然として叩き出される)。逆も同様。各 escape hatch は自分の責務だけを無効化し、もう片方の安全網は維持されます。
次に読む
- データ取得と DML、IEntry、Mock: 使い方ガイド
- 親項目・子サブクエリ・多対多: リレーション操作の典型例
- API リファレンス: IEloquent / Eloquent / MockEloquent:
IEntryを返す側 - API リファレンス: Scribe: SELECT 漏れ検知の元データを作る側
- ApexEloquent ガイド: ガイド目次に戻る