LoginSignup
9
11

More than 5 years have passed since last update.

[APEX]トリガーのテストクラス作成

Last updated at Posted at 2018-02-28

Salesforceのプログラム言語であるAPEXでトリガーがあるが、
SalesforceのTRAILHEADには入力規則系のテストの例しかなかったので、トリガーで値を更新する場合のテストクラスのメモ。

トリガーの例

AccountTriger
trigger AccountTrigger on Account (before insert, before update) {

    // 登録 *************************************************
    if (Trigger.isInsert) {
        for (Account n : System.Trigger.new) {
            n.Name = n.Name + '_insert';
        }
    }
    // 更新 *************************************************
    if (Trigger.isUpdate) {
        for (Account n : System.Trigger.new) {
            n.Name = n.Name + '_update';
        }
    }
}

テストクラスの例

AccountTriggerTest
@isTest
private class AccountTriggerTest {

    /*
     * 登録時のテスト
     */
    @isTest static void test_create() {
        // テストデータ作成
        Account insAcc = CreateAccount();

        // テスト開始
        Test.startTest();
        Database.SaveResult result = Database.insert(insAcc, false);
        Test.stopTest();

        // 検証
        Account accResult = [Select Id,Name from Account limit 1];
        System.assertEquals('_insert', accResult.Name.right(7));

    }


    /*
     * 更新時のテスト
     */
    @isTest static void test_update() {
        // テストデータ作成
        Account insAcc = CreateAccount();
        insert insAcc;

        Test.startTest();
        Database.SaveResult result = Database.update(insAcc, false);
        Test.stopTest();

        // 検証
        Account accResult = [Select Id,Name from Account limit 1];
        System.assertEquals('_update', accResult.Name.right(7));
    }



    /*
     * 顧客作成
     */
    private static Account CreateAccount() {

        Account acc = new Account(
          Name = 'Test'
        );
        return acc;

    }

}

2018.03.09追記

こちらのサイトでとても参考になることが記載されていました。日本語ではこちらがとてもわかりやすく説明しています。

・Force.com IDEのトリガ作成ウィザードでは提供されない、トリガ内での大量レコード処理への対処
・(isInsert, isBefore, etc…)のわかりづらい7つのトリガ起動イベントの可読性とメンテ性の向上
・Trigger.oldと Trigger.Newが、それぞれ利用可能な条件の間違い防止
・非同期メソッドの実装

トリガの中身自体は外部のクラスを呼び出すだけで、外部のクラスに詳細を記載します。
そうすると、テストクラス自体も普通のクラスのテストをするだけなので、かなり書きやすいですね。

AccountTrigger
trigger AccountTrigger on Account (after delete, after insert, after undelete, 
after update, before delete, before insert, before update) {
    AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size);

    if(Trigger.isInsert && Trigger.isBefore){
        handler.OnBeforeInsert(Trigger.new);
    }
    else if(Trigger.isInsert && Trigger.isAfter){
        handler.OnAfterInsert(Trigger.new);
        AccountTriggerHandler.OnAfterInsertAsync(Trigger.newMap.keySet());
    }

    else if(Trigger.isUpdate && Trigger.isBefore){
        handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap);
    }
    else if(Trigger.isUpdate && Trigger.isAfter){
        handler.OnAfterUpdate(Trigger.old, Trigger.new, Trigger.newMap);
        AccountTriggerHandler.OnAfterUpdateAsync(Trigger.newMap.keySet());
    }

    else if(Trigger.isDelete && Trigger.isBefore){
        handler.OnBeforeDelete(Trigger.old, Trigger.oldMap);
    }
    else if(Trigger.isDelete && Trigger.isAfter){
        handler.OnAfterDelete(Trigger.old, Trigger.oldMap);
        AccountTriggerHandler.OnAfterDeleteAsync(Trigger.oldMap.keySet());
    }

    else if(Trigger.isUnDelete){
        handler.OnUndelete(Trigger.new);    
    }
}
AccountTriggerHandler

public with sharing class AccountTriggerHandler {
    private boolean m_isExecuting = false;
    private integer BatchSize = 0;

    public AccountTriggerHandler(boolean isExecuting, integer size){
        m_isExecuting = isExecuting;
        BatchSize = size;
    }

    public void OnBeforeInsert(Account[] newAccounts){
        //Example usage
        for(Account newAccount : newAccounts){
            if(newAccount.AnnualRevenue == null){
                newAccount.AnnualRevenue.addError('Missing annual revenue');
            }
        }
    }

    public void OnAfterInsert(Account[] newAccounts){

    }

    @future public static void OnAfterInsertAsync(Set<ID> newAccountIDs){
        //Example usage
        List<Account> newAccounts = [select Id, Name from Account where Id IN :newAccountIDs];
    }

    public void OnBeforeUpdate(Account[] oldAccounts, Account[] updatedAccounts, Map<ID, Account> accountMap){
        //Example Map usage
        Map<ID, Contact> contacts = new Map<ID, Contact>( [select Id, FirstName, LastName, Email from Contact where AccountId IN :accountMap.keySet()] );
    }

    public void OnAfterUpdate(Account[] oldAccounts, Account[] updatedAccounts, Map<ID, Account> accountMap){

    }

    @future public static void OnAfterUpdateAsync(Set<ID> updatedAccountIDs){
        List<Account> updatedAccounts = [select Id, Name from Account where Id IN :updatedAccountIDs];
    }

    public void OnBeforeDelete(Account[] accountsToDelete, Map<ID, Account> accountMap){

    }

    public void OnAfterDelete(Account[] deletedAccounts, Map<ID, Account> accountMap){

    }

    @future public static void OnAfterDeleteAsync(Set<ID> deletedAccountIDs){

    }

    public void OnUndelete(Account[] restoredAccounts){

    }

    public boolean IsTriggerContext{
        get{ return m_isExecuting;}
    }

    public boolean IsVisualforcePageContext{
        get{ return !IsTriggerContext;}
    }

    public boolean IsWebServiceContext{
        get{ return !IsTriggerContext;}
    }

    public boolean IsExecuteAnonymousContext{
        get{ return !IsTriggerContext;}
    }
}

あとがき

ちなみに更新の場合、既存のレコードに対してupdateする方法もあるが、insertしてupdateした方が良い。

参考

Apexトリガのテスト
トリガのテストメソッドの作成方法について
[APEX]バッチのテストクラス作成
[salesforce]Force.comプログラミングのベストプラクティス集
[salesforce]トリガーテンプレート

9
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
11