Salesforceのプログラム言語であるAPEXでトリガーがあるが、
SalesforceのTRAILHEADには入力規則系のテストの例しかなかったので、トリガーで値を更新する場合のテストクラスのメモ。
トリガーの例
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';
}
}
}
テストクラスの例
@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が、それぞれ利用可能な条件の間違い防止
・非同期メソッドの実装
トリガの中身自体は外部のクラスを呼び出すだけで、外部のクラスに詳細を記載します。
そうすると、テストクラス自体も普通のクラスのテストをするだけなので、かなり書きやすいですね。
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);
}
}
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]トリガーテンプレート