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

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

# トリガーの例

```java: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';
        }
    }
}

```

# テストクラスの例

```java: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追記
[こちら](http://www.embracingthecloud.com/2010/07/08/ASimpleTriggerTemplateForSalesforce.aspx)のサイトでとても参考になることが記載されていました。日本語では[こちら](http://deferloader.blog.uhuru.co.jp/?p=2187)がとてもわかりやすく説明しています。

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

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

```java: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);	
	}
}
```

```java: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トリガのテスト](https://trailhead.salesforce.com/ja/modules/apex_testing/units/apex_testing_triggers)
[トリガのテストメソッドの作成方法について](https://help.salesforce.com/articleView?id=000004325&language=ja&type=1)
[[APEX]バッチのテストクラス作成](https://qiita.com/naoto_koyama/items/292eff57c780b2ee8c6b)
[[salesforce]Force.comプログラミングのベストプラクティス集](http://deferloader.blog.uhuru.co.jp/?p=2185#comments)
[[salesforce]トリガーテンプレート](http://deferloader.blog.uhuru.co.jp/?p=2187)