API リファレンス: SPersona

Apex Stem ドキュメント
Apex StemApexBlueprintSPersonarunAsAPI Reference
System.runAs で使う制限ユーザー (ペルソナ) を生成するビルダー。Profile / PermissionSet の割り当て、安全な既定値、owner() / sharedWith() と組んで敵対的データを宣言する使い方まで。

API リファレンス: SPersona

SPersona は、System.runAs で使う制限ユーザー (ペルソナ) を生成するためのビルダーです。SBlueprint と同じくイミュータブルで、各メソッドが新しいインスタンスを返します。

権限まわりのテストを書こうとすると、テストユーザーの作成に細かい落とし穴が並びます (一意な Username、mixed DML、ロケール既定値、Profile 名の解決)。SPersona はその「正しい 1 つの実装」を 1 クラスに集約しています。UserFactory を自前で書かないでください。

Static ファクトリ

メソッド用途
SPersona.of(String name)起点。短いペルソナ名 ('sales-rep' など) を渡す。LastName などに使われる

組み立て

メソッド用途
profile(String profileName)Profile を名前で指定 ('標準ユーザー' / 'Standard User' など)。create() 時に解決され、テスト実行全体でキャッシュされる
permissionSets(String permissionSetName)割り当てる PermissionSet を API 名で 1 つ追加 (ラベルではない)
permissionSets(List<String> permissionSetNames)同上、複数まとめて
set(String fieldName, Object value)任意の User 項目を上書き。SBlueprint.set() と同じく last wins

生成

メソッド戻り値用途
create()UserProfile を解決し、安全な既定値で User を組み立て、PermissionSetAssignment ごと insert して返す
APEX
User rep = SPersona.of('sales-rep')
  .profile('標準ユーザー')
  .permissionSets('InvoiceReadOnly')
  .set('LanguageLocaleKey', 'ja')
  .create();

知っておくべき挙動

  • Username は UUID で払い出されるので、テストの並列実行でも衝突しません
  • ロケール系の既定値は実行ユーザー基準で埋まります (org に依存しない)。変えたければ set() で上書き
  • mixed DML を内部で回避しています (System.runAs でラップして insert)。通常の DML と混ぜて呼んで構いません
  • create() はテストコンテキスト専用です。System.runAs に依存するため、本番コードからは呼べません
  • キャッシュはしません。呼ぶたびに新しいユーザーが作られます (Profile 名 → Id の解決だけがキャッシュされます)
  • ⚠️ Profile 名はロケール依存です。日本語 org では '標準ユーザー'、英語 org では 'Standard User' になります

敵対的データとセットで使う

SPersona が本領を発揮するのは、SBlueprintowner() / sharedWith() と組み合わせたときです。「管理者が所有し、テスト対象のペルソナには最小限だけ共有する」という敵対的なデータを宣言できます。

APEX
User admin = SPersona.of('admin').profile('システム管理者').create();
User rep   = SPersona.of('sales-rep').profile('標準ユーザー').create();
 
SOrchestrator orchestrator = SOrchestrator.start()
  .add(
    SBlueprint.of(Invoice__c.class).alias('inv')
      .owner(admin)                //  admin 所有
      .sharedWith(rep, 'Read'));   //  rep には Read だけ
orchestrator.create();
 
System.runAs(rep) {
  // rep から何が見えるか / 何が書けるかを検証する
}

sharedWith の有無が、そのまま可視性の期待値になります。共有を宣言していないレコードが runAs の中で見えてしまったら、それは共有設定の穴です。

プロジェクト側での集約

Blueprints.cls と同じ考え方で、プロジェクト固有のペルソナは単一の Personas.cls に集約します。テストごとに Profile 名や PermissionSet 名を直書きすると、org 側の改名で一斉に壊れたときの修正箇所が散らばります。

APEX
public with sharing class Personas {
  /** 一般の営業担当 */
  public static User salesRep() {
    return SPersona.of('sales-rep')
      .profile('標準ユーザー')
      .permissionSets('InvoiceReadOnly')
      .create();
  }
}

関連ドキュメント