Lv. 5 の解答
アウトライン
1. Apex トリガを作成
1-1. Apex トリガを作成します。
OpportunityTrigger.trigger
sfdx force:apex:trigger:create -d force-app/main/default/triggers -e "before insert,before update,before delete,after insert,after update,after delete,after undelete" -n OpportunityTrigger -s Opportunity -t ApexTrigger
OpportunityTrigger.trigger
trigger OpportunityTrigger on Opportunity(
before insert,
before update,
before delete,
after insert,
after update,
after delete,
after undelete
) {
FAT_CommonTriggerHandler handler = FAT_CommonTriggerHandler.create(
Opportunity.class
);
handler.invoke();
}
CaseTrigger.trigger
sfdx force:apex:trigger:create -d force-app/main/default/triggers -e "before insert,before update,before delete,after insert,after update,after delete,after undelete" -n CaseTrigger -s Case -t ApexTrigger
CaseTrigger.trigger
trigger CaseTrigger on Case(
before insert,
before update,
before delete,
after insert,
after update,
after delete,
after undelete
) {
FAT_CommonTriggerHandler handler = FAT_CommonTriggerHandler.create(
Case.class
);
handler.invoke();
}
1-2. コードをフォーマットします。
yarn prettier
1-3. スクラッチ組織へプッシュします。
sfdx force:source:push -u demo
2. カスタム表示ラベルを追加
2-1. カスタム表示ラベルを追加します。
CustomLabels.labels-meta.xml
- CLOSED_WON_CANNOT_BE_DELETED
- HIGH_CASE_IS_CREATED
<?xml version="1.0" encoding="UTF-8" ?>
<CustomLabels xmlns="http://soap.sforce.com/2006/04/metadata">
<labels>
<fullName>SLA_EXPIRATION_DATE_REQUIRED</fullName>
<categories>ERROR</categories>
<language>ja</language>
<protected>false</protected>
<shortDescription>SLA_EXPIRATION_DATE_REQUIRED</shortDescription>
<value>SLA が設定されている場合は有効期限も入力してください。</value>
</labels>
<labels>
<fullName>SLA_SERIAL_NUMBER_REQUIRED</fullName>
<categories>ERROR</categories>
<language>ja</language>
<protected>false</protected>
<shortDescription>SLA_SERIAL_NUMBER_REQUIRED</shortDescription>
<value
>SLA が設定されている場合はシリアルナンバーも入力してください。</value>
</labels>
<labels>
<fullName>CLOSED_WON_CANNOT_BE_DELETED</fullName>
<categories>ERROR</categories>
<language>ja</language>
<protected>false</protected>
<shortDescription>CLOSED_WON_CANNOT_BE_DELETED</shortDescription>
<value>成約済みの商談は削除できません。</value>
</labels>
<labels>
<fullName>HIGH_CASE_IS_CREATED</fullName>
<categories>INFO</categories>
<language>ja</language>
<protected>false</protected>
<shortDescription>HIGH_CASE_IS_CREATED</shortDescription>
<value>優先度の高いケースが作成されました!</value>
</labels>
</CustomLabels>
2-2. コードをフォーマットします。
yarn prettier
2-3. スクラッチ組織へプッシュします。
sfdx force:source:push -u demo
3. Apex クラスを作成
3-1. Apex クラスを作成します。
OpportunityTriggerValidation.cls
sfdx force:apex:class:create -d force-app/main/default/classes -n OpportunityTriggerValidation -t DefaultApexClass
OpportunityTriggerValidation.cls
@SuppressWarnings('PMD.EmptyStatementBlock,PMD.ApexDoc')
public with sharing class OpportunityTriggerValidation implements FAT_ITriggerObserver {
private static final String CLOSED_WON_CANNOT_BE_DELETED = System.Label.CLOSED_WON_CANNOT_BE_DELETED;
private static final String CLOSED_WON = 'Closed Won';
private final FAT_CommonLogger logger = FAT_CommonLogger.getInstance();
public class CustomException extends Exception {
}
private void setMethodName(String methodName) {
logger.setClassName(OpportunityTriggerValidation.class.getName());
logger.setMethodName(methodName);
}
@TestVisible
private void preventDeletion(List<Opportunity> opportunities) {
this.setMethodName('preventDeletion');
for (Opportunity opportunity : opportunities) {
logger.store(LoggingLevel.DEBUG, 'opportunity: ' + opportunity.Id);
Boolean closedWon = CLOSED_WON.equals(opportunity.StageName);
if (closedWon) {
opportunity.StageName.addError(CLOSED_WON_CANNOT_BE_DELETED);
String errorMessage = CLOSED_WON_CANNOT_BE_DELETED;
CustomException e = new CustomException();
e.setMessage(errorMessage);
throw e;
}
}
}
public void onBeforeInsert(FAT_CommonTriggerHandler handler) {
}
public void onBeforeUpdate(FAT_CommonTriggerHandler handler) {
}
public void onBeforeDelete(FAT_CommonTriggerHandler handler) {
this.preventDeletion((List<Opportunity>) handler.oldObjects);
}
public void onAfterInsert(FAT_CommonTriggerHandler handler) {
}
public void onAfterUpdate(FAT_CommonTriggerHandler handler) {
}
public void onAfterDelete(FAT_CommonTriggerHandler handler) {
}
public void onAfterUndelete(FAT_CommonTriggerHandler handler) {
}
}
CaseTriggerService.cls
sfdx force:apex:class:create -d force-app/main/default/classes -n CaseTriggerService -t DefaultApexClass
CaseTriggerService.cls
@SuppressWarnings('PMD.EmptyStatementBlock,PMD.ApexDoc')
public with sharing class CaseTriggerService implements FAT_ITriggerObserver {
private static final String HIGH_CASE_IS_CREATED = System.Label.HIGH_CASE_IS_CREATED;
private static final String PRIORITY_HIGH = 'High';
private final FAT_CommonLogger logger = FAT_CommonLogger.getInstance();
private void setMethodName(String methodName) {
logger.setClassName(CaseTriggerService.class.getName());
logger.setMethodName(methodName);
}
@TestVisible
private void postFeedItems(List<Case> cases) {
this.setMethodName('postFeedItems');
List<FeedItem> feedItems = new List<FeedItem>();
for (Case newCase : cases) {
logger.store(LoggingLevel.DEBUG, 'New Case: ' + newCase.Id);
Boolean isHigh = PRIORITY_HIGH.equals(newCase.Priority);
if (isHigh) {
FeedItem feedItem = new FeedItem();
feedItem.ParentId = newCase.Id;
feedItem.Body = HIGH_CASE_IS_CREATED;
feedItems.add(feedItem);
}
}
Database.insert(feedItems, false);
}
public void onBeforeInsert(FAT_CommonTriggerHandler handler) {
}
public void onBeforeUpdate(FAT_CommonTriggerHandler handler) {
}
public void onBeforeDelete(FAT_CommonTriggerHandler handler) {
}
public void onAfterInsert(FAT_CommonTriggerHandler handler) {
this.postFeedItems((List<Case>) handler.newObjects);
}
public void onAfterUpdate(FAT_CommonTriggerHandler handler) {
}
public void onAfterDelete(FAT_CommonTriggerHandler handler) {
}
public void onAfterUndelete(FAT_CommonTriggerHandler handler) {
}
}
3-2. コードをフォーマットします。
yarn prettier
3-3. スクラッチ組織へプッシュします。
sfdx force:source:push -u demo
4. カスタムメタデータ型を作成
FAT_TriggerObserver.OpportunityTriggerValidation.md-meta.xml
4-1.
FAT_TriggerObserver.AccountTriggerService.md-meta.xml
をコピーしてカスタムメタデータ型を作成します。
FAT_TriggerObserver.OpportunityTriggerValidation.md-meta.xml
<?xml version="1.0" encoding="UTF-8" ?>
<CustomMetadata
xmlns="http://soap.sforce.com/2006/04/metadata"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
>
<label>OpportunityTriggerValidation</label>
<protected>false</protected>
<values>
<field>Active__c</field>
<value xsi:type="xsd:boolean">true</value>
</values>
<values>
<field>AfterDelete__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>AfterInsert__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>AfterUndelete__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>AfterUpdate__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>ApexClass__c</field>
<value xsi:type="xsd:string">OpportunityTriggerValidation</value>
</values>
<values>
<field>BeforeDelete__c</field>
<value xsi:type="xsd:boolean">true</value>
</values>
<values>
<field>BeforeInsert__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>BeforeUpdate__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>SObject__c</field>
<value xsi:type="xsd:string">Opportunity</value>
</values>
</CustomMetadata>
FAT_TriggerObserver.CaseTriggerService.md-meta.xml
4-2.
FAT_TriggerObserver.AccountTriggerService.md-meta.xml
をコピーしてカスタムメタデータ型を作成します。
FAT_TriggerObserver.CaseTriggerService.md-meta.xml
<?xml version="1.0" encoding="UTF-8" ?>
<CustomMetadata
xmlns="http://soap.sforce.com/2006/04/metadata"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
>
<label>CaseTriggerService</label>
<protected>false</protected>
<values>
<field>Active__c</field>
<value xsi:type="xsd:boolean">true</value>
</values>
<values>
<field>AfterDelete__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>AfterInsert__c</field>
<value xsi:type="xsd:boolean">true</value>
</values>
<values>
<field>AfterUndelete__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>AfterUpdate__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>ApexClass__c</field>
<value xsi:type="xsd:string">CaseTriggerService</value>
</values>
<values>
<field>BeforeDelete__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>BeforeInsert__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>BeforeUpdate__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>SObject__c</field>
<value xsi:type="xsd:string">Case</value>
</values>
</CustomMetadata>
4-3. コードをフォーマットします。
yarn prettier
4-4. スクラッチ組織へプッシュします。
sfdx force:source:push -u demo
5. Apex テストクラスを作成
5-1. Apex テストクラスを作成します。
OpportunityTriggerValidationTest.cls
sfdx force:apex:class:create -d force-app/test/default/classes -n OpportunityTriggerValidationTest -t ApexUnitTest
OpportunityTriggerValidationTest.cls
@isTest(SeeAllData=false)
private class OpportunityTriggerValidationTest {
private static final String CLOSED_WON_CANNOT_BE_DELETED = System.Label.CLOSED_WON_CANNOT_BE_DELETED;
private static final String CLOSED_WON = 'Closed Won';
private static OpportunityTriggerValidation validation = new OpportunityTriggerValidation();
private static FAT_CommonTriggerHandler handler = FAT_CommonTriggerHandler.create(
Opportunity.class
);
@isTest
static void preventDeletion() {
List<Opportunity> opportunities = new List<Opportunity>();
Opportunity opportunity = new Opportunity();
opportunity.StageName = CLOSED_WON;
opportunities.add(opportunity);
Test.startTest();
List<Boolean> exceptions = new List<Boolean>();
try {
validation.preventDeletion(opportunities);
} catch (Exception e) {
exceptions.add(true);
String message = e.getMessage();
System.assertEquals(
CLOSED_WON_CANNOT_BE_DELETED,
message,
'preventDeletion'
);
}
Test.stopTest();
for (Boolean b : exceptions) {
System.assertEquals(true, b, 'preventDeletion');
}
}
@isTest
static void onBeforeInsert() {
Test.startTest();
validation.onBeforeInsert(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onBeforeInsert');
}
@isTest
static void onBeforeUpdate() {
Test.startTest();
validation.onBeforeUpdate(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onBeforeUpdate');
}
@isTest
static void onBeforeDelete() {
List<Opportunity> opportunities = new List<Opportunity>();
handler.oldObjects = opportunities;
Test.startTest();
validation.onBeforeDelete(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onBeforeDelete');
}
@isTest
static void onAfterInsert() {
Test.startTest();
validation.onAfterInsert(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onAfterInsert');
}
@isTest
static void onAfterUpdate() {
Test.startTest();
validation.onAfterUpdate(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onAfterUpdate');
}
@isTest
static void onAfterDelete() {
Test.startTest();
validation.onAfterDelete(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onAfterDelete');
}
@isTest
static void onAfterUndelete() {
Test.startTest();
validation.onAfterUndelete(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onAfterUndelete');
}
}
OpportunityTestUtils.cls
sfdx force:apex:class:create -d force-app/test/default/classes -n OpportunityTestUtils -t ApexUnitTest
OpportunityTestUtils.cls
@SuppressWarnings('PMD.ApexDoc')
@isTest(SeeAllData=false)
public with sharing class OpportunityTestUtils {
public static List<Opportunity> createNormalOpportunities() {
List<Opportunity> opportunities = new List<Opportunity>();
Opportunity opportunity1 = new Opportunity();
opportunity1.Name = 'Demo 1';
opportunity1.StageName = 'Closed Won';
opportunity1.CloseDate = Date.today();
opportunities.add(opportunity1);
return opportunities;
}
public static List<Opportunity> selectOpportunities() {
return [
SELECT Id, Name, StageName, CloseDate
FROM Opportunity
ORDER BY Name ASC
LIMIT 50000
];
}
public static void insertOpportunities(List<Opportunity> opportunities) {
List<Database.SaveResult> results = Database.insert(opportunities, false);
}
public static void updateOpportunities(List<Opportunity> opportunities) {
List<Database.SaveResult> results = Database.update(opportunities, false);
}
public static void deleteOpportunities(List<Opportunity> opportunities) {
List<Database.DeleteResult> results = Database.delete(opportunities, false);
}
public static void undeleteOpportunities(List<Opportunity> opportunities) {
List<Database.UndeleteResult> results = Database.undelete(
opportunities,
false
);
}
}
OpportunityTriggerTest.cls
sfdx force:apex:class:create -d force-app/test/default/classes -n OpportunityTriggerTest -t ApexUnitTest
OpportunityTriggerTest.cls
@isTest(SeeAllData=false)
private class OpportunityTriggerTest {
@testSetup
static void setup() {
List<Opportunity> opportunities = OpportunityTestUtils.createNormalOpportunities();
OpportunityTestUtils.insertOpportunities(opportunities);
}
@isTest
static void invokeDelete() {
List<Opportunity> opportunities = OpportunityTestUtils.selectOpportunities();
Test.startTest();
OpportunityTestUtils.deleteOpportunities(opportunities);
Test.stopTest();
System.assertNotEquals(0, opportunities.size(), 'invokeDelete');
}
}
CaseTriggerServiceTest.cls
sfdx force:apex:class:create -d force-app/test/default/classes -n CaseTriggerServiceTest -t ApexUnitTest
CaseTriggerServiceTest.cls
@isTest(SeeAllData=false)
private class CaseTriggerServiceTest {
private static CaseTriggerService service = new CaseTriggerService();
private static FAT_CommonTriggerHandler handler = FAT_CommonTriggerHandler.create(
Case.class
);
@isTest
static void postFeedItems() {
List<Case> cases = new List<Case>();
Case newCase = new Case();
newCase.Priority = 'High';
newCase.Origin = 'Phone';
newCase.Status = 'New';
cases.add(newCase);
Test.startTest();
service.postFeedItems(cases);
Test.stopTest();
System.assertNotEquals(null, cases, 'postFeedItems');
}
@isTest
static void onBeforeInsert() {
Test.startTest();
service.onBeforeInsert(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onBeforeInsert');
}
@isTest
static void onBeforeUpdate() {
Test.startTest();
service.onBeforeUpdate(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onBeforeUpdate');
}
@isTest
static void onBeforeDelete() {
Test.startTest();
service.onBeforeDelete(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onBeforeDelete');
}
@isTest
static void onAfterInsert() {
List<Case> cases = new List<Case>();
handler.newObjects = cases;
Test.startTest();
service.onAfterInsert(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onAfterInsert');
}
@isTest
static void onAfterUpdate() {
Test.startTest();
service.onAfterUpdate(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onAfterUpdate');
}
@isTest
static void onAfterDelete() {
Test.startTest();
service.onAfterDelete(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onAfterDelete');
}
@isTest
static void onAfterUndelete() {
Test.startTest();
service.onAfterUndelete(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onAfterUndelete');
}
}
CaseTestUtils.cls
sfdx force:apex:class:create -d force-app/test/default/classes -n CaseTestUtils -t ApexUnitTest
CaseTestUtils.cls
@SuppressWarnings('PMD.ApexDoc')
@isTest(SeeAllData=false)
public with sharing class CaseTestUtils {
public static List<Case> createNormalCases() {
List<Case> cases = new List<Case>();
Case case1 = new Case();
case1.Priority = 'High';
case1.Origin = 'Phone';
case1.Status = 'New';
cases.add(case1);
return cases;
}
public static List<Case> selectCases() {
return [
SELECT Id, CaseNumber, Priority, Origin, Status
FROM Case
ORDER BY CaseNumber ASC
LIMIT 50000
];
}
public static void insertCases(List<Case> cases) {
List<Database.SaveResult> results = Database.insert(cases, false);
}
public static void updateCases(List<Case> cases) {
List<Database.SaveResult> results = Database.update(cases, false);
}
public static void deleteCases(List<Case> cases) {
List<Database.DeleteResult> results = Database.delete(cases, false);
}
public static void undeleteCases(List<Case> cases) {
List<Database.UndeleteResult> results = Database.undelete(cases, false);
}
}
CaseTriggerTest.cls
sfdx force:apex:class:create -d force-app/test/default/classes -n CaseTriggerTest -t ApexUnitTest
CaseTriggerTest.cls
@isTest(SeeAllData=false)
private class CaseTriggerTest {
@testSetup
static void setup() {
List<Case> cases = CaseTestUtils.createNormalCases();
CaseTestUtils.insertCases(cases);
}
@isTest
static void invokeDelete() {
List<Case> cases = CaseTestUtils.selectCases();
Test.startTest();
CaseTestUtils.deleteCases(cases);
Test.stopTest();
System.assertNotEquals(0, cases.size(), 'invokeDelete');
}
}
5-2. コードをフォーマットします。
yarn prettier
5-3. スクラッチ組織へプッシュします。
sfdx force:source:push -u demo
5-4. Apex テストを実行して現在のコードカバー率を確認します。
$Env:SFDX_IMPROVED_CODE_COVERAGE = "true"
sfdx force:apex:test:run -c -l RunLocalTests -r human -u demo
=== Test Summary
NAME VALUE
─────────────────── ───────────────────────────────────────────────────────────
Outcome Passed
Tests Ran 85
Passing 85
Failing 0
Skipped 0
Pass Rate 100%
Fail Rate 0%
Test Run Coverage 100%
Org Wide Coverage 100%
(...以下省略...)
コードカバー率は 100%になっています!
動作確認してみましょう。商談画面を開いて、想定通りの挙動かどうかを確認しましょう。 また、ケース画面を開いて、想定通りの挙動かどうかを確認しましょう。
... いかがでしたか?