TraceFlow でテストの経路を検証する
TraceFlow でテストの経路を検証する
TraceFlow は、直前に実行された Trace の状態を覗くための静的ユーティリティです。テストで「Usecase がどの経路で終わったか」を区別するのに使います。
主な検証メソッド
| メソッド | 検証内容 |
|---|---|
TraceFlow.isLastFinish() | 直前の Trace が finish() で終わったか |
TraceFlow.isLastSkip() | 直前の Trace が skip() で終わったか |
TraceFlow.isLastAbort() | 直前の Trace が abort() で終わったか |
TraceFlow.lastHistoryContains(text) | 最後の 1 エントリに文字列が含まれるか |
TraceFlow.contains(text) | 履歴全体のどこかに文字列が含まれるか |
TraceFlow.lastHistory() | 直前の TraceHistory オブジェクトを取得 |
TraceFlow.usageOf(name) | その名前のコンテキストのガバナ消費 (exclusive 合計、v1.2.0+) |
TraceFlow.lastUsage() | 直近に閉じた1 コンテキストのガバナ消費 (バルク IT では usageOf を使う) |
lastHistoryContains と contains の使い分け
lastHistoryContains が見るのは最後の 1 エントリだけです。そのため、あとから t.log(...) を 1 行足しただけでテストが落ちます。
finish/skip/abortの理由メッセージを縛る →lastHistoryContains。直後のエントリなので順序が安定します- 途中の
t.log(...)の内容を縛る / ログ追加に強くしたい →contains
Assert.isTrue(TraceFlow.contains('3 件の商談に業種をコピー')); // 履歴のどこにあっても通る
どちらも null を渡すと false を返します。
invoke() が void でも経路で検証できる
戻り値が void の Usecase は、テストの観察対象が副作用 (DML や field 更新の中身) に偏りがちです。それでも TraceFlow を組み合わせれば、「副作用」と「経路」の 2 軸で挙動を縛れます。これは ApexTrace が Apex Stem に標準装備されている理由のひとつです。
コード例: skip と finish を区別する
「対象がなくてスキップされた」と「正常に完了した」を、別々のテストとして書きます。
@isTest
static void testInvoke_WhenOpportunityHasAccount_ThenIndustryCopied() {
Trace t = Trace.of('正常系: 商談に親取引先の業種がコピーされること');
t.start();
MockEntry oppEntry = MockEntry.of(Opportunity.class)
.alias('opp').autoId(1)
.setParent('AccountId',
MockEntry.of(Account.class).set('Industry', 'Technology'));
MockEloquent mock = (new MockEloquent())
.attach(CopyAccountIndustryToOpportunityUsecase.LBL_FETCH, new List<IEntry>{ oppEntry });
(new CopyAccountIndustryToOpportunityUsecase(
new Set<Id>{ oppEntry.getAliasId('opp') }, mock
)).invoke();
Assert.areEqual(1, mock.upsertedRecordsAt(CopyAccountIndustryToOpportunityUsecase.LBL_UPDATE).size());
Assert.isTrue(TraceFlow.isLastFinish());
t.finish();
}
@isTest
static void testInvoke_WhenNoOpportunityIds_ThenSkipped() {
Trace t = Trace.of('正常系: 対象の商談がないときスキップされること');
t.start();
MockEloquent mock = new MockEloquent();
(new CopyAccountIndustryToOpportunityUsecase(
new Set<Id>(), mock
)).invoke();
Assert.isTrue(TraceFlow.isLastSkip());
t.finish();
}
両方のテストとも、戻り値ではない「経路」を TraceFlow で観察しています。finish パスを通ったか skip パスを通ったかが、副作用 (upsertedRecords の中身) とは別の軸で検証できます。
ログ内容の検証も可能です。
Assert.isTrue(TraceFlow.lastHistoryContains('対象の商談がないため終了'));
「想定通りのメッセージで skip したか」までを縛れます。
観測窓を Act に揃える: discardArrange() (v1.4.0+)
isLastFinish() / isLastSkip() / isLastAbort() が見るのは「最後に閉じたコンテキスト」です。ところが Trace の履歴はテストメソッドの先頭から溜まり続けるため、Arrange でレコードを作った DML がトリガー経由で Usecase を走らせていると、その分も履歴に残ります。
問題になるのは、Act が何も起こさなかったときです。Act 側にコンテキストが 1 つも積まれないと、isLastSkip() が読むのは Arrange のコンテキストになります。つまり Act を検証したつもりで Arrange を検証している状態が起こり得ます。
TraceFlow.discardArrange() を Arrange と Act の境界に置くと、履歴がそこで区切られ、以降のアサートは Act だけを見ます。
setupAccountWithOpportunities(); // Arrange (トリガー経由で Usecase が走る)
TraceFlow.discardArrange(); // ← ここ
Test.startTest(); // ← と、ここは同じ境界
new ResummarizeUsecase(ids).invoke();
Test.stopTest();
Assert.isTrue(TraceFlow.isLastSkip()); // Act の経路だけを見る
Test.startTest() を置くのと同じ判断なので、新しく覚える概念はありません。書く場所も隣です。
⚠️ Usecase のコンテキストが開いている最中には呼べません。 境界で呼んでいる限り、開いているのはテスト自身の
Traceが最大 1 つなので問題になりませんが、それ以上開いているとTraceExceptionになります。
関連ドキュメント
- Trace のライフサイクル: 4 メソッドと 3 つの終了パス
- ガバナ消費を縛る: TraceUsage によるバルクの保険
- ApexTrace ガイド: ガイド目次に戻る