Salesforce 触发器测试错误
Salesforce Trigger Test Error
您好!
我正在 Salesforce 中对触发器进行单元测试,我一直遇到一个我似乎无法解决的错误,所以我希望有更多经验的人可以帮助我回到正轨。我在 Google 上四处寻找并多次弄乱我的代码结构,但我找不到解决方案。
用途:
我的任务是编写一个触发器来处理维护每个开发人员的案例排名所需的逻辑。每个开发人员都分配有案例,这些案例可能有也可能没有由业务确定的优先级。每个开发人员一次只能优先处理 10 个案例。任何其他情况在排名字段中只会有一个空值。如果插入、更新或删除具有排名的案例,则分配给具有排名的该开发人员的所有其他案例必须相应地自动更新。任何排名高于 10 的案例都将被剔除。
问题:
我已经完成了触发器和触发器处理程序 class 现在我正在编写单元测试以涵盖所有单元测试。当我完成我的第一个单元测试方法时,我收到一个引用工作流问题的错误。我发现并纠正了这个问题,但在我完成我的第二个单元测试方法后,我再次遇到同样的错误。我可以注释掉这两种方法中的任何一种和其他方法,但是每当我 运行 将它们放在一起时,我会在第一个方法上通过,而在第二个方法上失败,并出现相同的原始错误。
代码:
Trigger Code -
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/09/15
* @brief: This is a trigger for the Case object that will modify the rank of the Cases
* assigned to the developer based on a priority set by the Administrator.
***************************************************************************************/
trigger CaseRankTrigger on Case (before insert, before update, before delete) {
// trigger level variables
private static Boolean firstRun = true;
private RecordType ticketRecordType = [SELECT Id FROM RecordType WHERE SobjectType = 'Case' AND Name = 'Salesforce Service Ticket' LIMIT 1];
private List<Case> newTrigger = trigger.new;
private List<Case> currentTrigger = trigger.old;
private List<Case> qualifiedNewCases = new List<Case>();
private List<Case> qualifiedCurrentCases = new List<Case>();
// makes sure that the trigger only runs once
if (firstRun) {
firstRun = false;
// populate trigger Case lists
qualifyCases();
if (qualifiedNewCases.size() == 1 || qualifiedCurrentCases.size() == 1) {
// the CaseRankTriggerHandler constructor method takes (List<Case>, List<Case>, String)
if (trigger.isInsert) CaseRankTriggerHandler handler = new CaseRankTriggerHandler(qualifiedNewCases, qualifiedCurrentCases, 'Insert'); // if a new Case is being inserted into the database
if (trigger.isUpdate) CaseRankTriggerHandler handler = new CaseRankTriggerHandler(qualifiedNewCases, qualifiedCurrentCases, 'Update'); // if an existing Case is being updated
if (trigger.isDelete) CaseRankTriggerHandler handler = new CaseRankTriggerHandler(qualifiedNewCases, qualifiedCurrentCases, 'Delete'); // if an existing Case is deleted from the database
}
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/24/15
* @brief: The qualifyCases method populates a list of Cases for each trigger
* that are of the Salesforce Service Ticket record type only.
* @return: Void
***************************************************************************************/
public void qualifyCases() {
if (newTrigger != null ) {
for (Case c : newTrigger) {
if (c.recordTypeId == ticketRecordType.Id) {
qualifiedNewCases.add(c);
}
}
}
if (currentTrigger != null) {
for (Case c : currentTrigger) {
if (c.recordTypeId == ticketRecordType.Id) {
qualifiedCurrentCases.add(c);
}
}
}
}
}
Trigger Handler Code -
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/09/15
* @brief: This is a Case object trigger handler class that provides logic to the CaseRankTrigger for manipulating
* the ranks of all Cases assigned to a developer based on a priority that is set by an Administrator.
***************************************************************************************/
public with sharing class CaseRankTriggerHandler {
// class level variables
private static Boolean firstRun = true;
private static Boolean modify = false;
private static Integer MAX = 10;
private static Integer MIN = 1;
private List<Case> newTrigger {get; set;}
private List<Case> currentTrigger {get; set;}
private List<Case> cases {get; set;}
private List<Case> newList {get; set;}
private List<Case> currentList {get; set;}
private String developer {get; set;}
private Decimal newRank {get; set;}
private Decimal currentRank {get; set;}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/16/15
* @brief: Class constructor method.
* @return: Void
***************************************************************************************/
public CaseRankTriggerHandler(List<Case> newT, List<Case> oldT, String type) {
if (firstRun) { // makes sure that the trigger only runs once
firstRun = false;
InitializeTrigger(newT, oldT, type); // initializes the trigger
if (developer != null) { // skips trigger if DML is performed on a Case with no developer assigned
ModificationCheck(type); // determines if Cases need to be modified
if (modify) ModificationLogic(type); // modifies Cases if needed
}
}
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/16/15
* @brief: The InitializeTrigger method initializes the handler class based on the type of trigger fired.
* @return: Void
***************************************************************************************/
private void InitializeTrigger(List<Case> newT, List<Case> oldT, String type) {
if (type == 'Insert') {
this.newTrigger = newT;
this.developer = newTrigger[0].Resource_Assigned__c;
this.newRank = newTrigger[0].Case_Rank__c;
this.newList = [SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :developer AND Case_Rank__c != null AND Case_Rank__c = :newRank ORDER BY Case_Rank__c];
} else if (type == 'Update') {
this.newTrigger = newT;
this.currentTrigger = oldT;
this.developer = newTrigger[0].Resource_Assigned__c;
this.newRank = newTrigger[0].Case_Rank__c;
this.currentRank = currentTrigger[0].Case_Rank__c;
this.newList = [SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :developer AND Case_Rank__c != null AND Case_Rank__c = :newRank ORDER BY Case_Rank__c];
this.currentList = [SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :developer AND Case_Rank__c != null AND Case_Rank__c = :currentRank ORDER BY Case_Rank__c];
} else if (type == 'Delete') {
this.currentTrigger = oldT;
this.developer = currentTrigger[0].Resource_Assigned__c;
this.currentRank = currentTrigger[0].Case_Rank__c;
}
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/16/15
* @brief: The ModificationCheck method ensures various conditions are met, depending on the type
* of trigger that was fired, before modifying the ranks of the Cases assigned to the developer.
* @return: Void
***************************************************************************************/
private void ModificationCheck(String type) {
if (type == 'Insert') {
// the Case being inserted has a new rank not equal to null and if the assigned developer already has a Case with the
// same rank as the new rank, we will proceed to modification, if not the record will be inserted without modification.
if (newRank != null && !newList.isEmpty()) {
modify = true;
}
} else if (type == 'Update') {
// if the Case being updated has ranks with different values in both triggers we will proceed to the next check, if not the record is updated without modification.
if (newRank != currentRank) {
// if the Case being updated has a (new rank equal to null and a current rank not equal to 10) or
// if the Case being updated has a new rank not equal to null, we will proceed to the next check,
// if not the record is updated without modification.
if ((newRank == null && currentRank != 10) || newRank != null) {
// if the assigned developer on the Case being updated already has a Case with the same rank as the new or current rank, we will proceed to modification,
// if not the record is updated without modification.
if (!newList.isEmpty() || !currentList.isEmpty()) {
modify = true;
}
}
}
} else if (type == 'Delete') {
// if the Case being deleted has current rank not equal to null, we will proceed to modification, if not the record is deleted without modification.
if (currentRank != null) {
modify = true;
}
}
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/16/15
* @brief: If a Case rank needs to be updated the ModificationLogic method calls the appropriate
* computation method based on trigger type and the values of newRank and currentRank.
* @return: Void
***************************************************************************************/
private void ModificationLogic(String type) {
if (type == 'Insert') {
for (Case c : newTrigger) {
// calls the IncreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank greater than or equal to the new rank.
IncreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND Case_Rank__c >= :newRank ORDER BY Case_Rank__c]);
}
} else if (type == 'Update') {
for (Case c : newTrigger) {
if (currentRank == null) {
// if the current rank is null - calls the IncreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank greater than or equal to the new rank.
IncreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND Case_Rank__c >= :newRank ORDER BY Case_Rank__c]);
} else if (newRank == null) {
// if the new rank is null - calls the DecreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank greater than the current rank.
DecreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND Case_Rank__c > :currentRank ORDER BY Case_Rank__c]);
} else if (newRank > currentRank) {
// if the new rank is greater than the current rank - calls the DecreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank less than or equal to the new rank and greater than to the current rank.
DecreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND (Case_Rank__c <= :newRank AND Case_Rank__c > :currentRank) ORDER BY Case_Rank__c]);
} else if (newRank < currentRank) {
// if the new rank is less than the current rank - calls the IncreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank a.
IncreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND (Case_Rank__c >= :newRank AND Case_Rank__c < :currentRank) ORDER BY Case_Rank__c]);
}
}
} else if (type == 'Delete') {
for (Case c : currentTrigger) {
// calls the DecreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank greater than the current rank.
DecreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :currentTrigger AND Resource_Assigned__c = :developer AND Case_Rank__c > :currentRank ORDER BY Case_Rank__c]);
}
}
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/16/15
* @brief: The DecreaseCaseRank method provides the logic required to properly
* decrease or null out the ranks of the Cases assigned the the developer.
* @return: Void
***************************************************************************************/
private void DecreaseCaseRank(List<Case> cases) {
// if the list of Cases passed in by the ModificationLogic method isn't empty then it will iterate through the
// list and decrease their ranks by 1 or null out the rank if it is not within the acceptable limits (1-10).
if (!cases.isEmpty()) {
for (Case c : cases) {
if (c.Case_Rank__c >= 1 && c.Case_Rank__c <= 10) {
c.Case_Rank__c = c.Case_Rank__c - 1;
} else {
c.Case_Rank__c = null;
}
}
update cases;
}
return;
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/16/15
* @brief: The IncreaseCaseRank method provides the logic required to properly
* increase or null out the ranks of the Cases assigned the the developer.
* @return: Void
***************************************************************************************/
private void IncreaseCaseRank(List<Case> cases) {
// if the list of Cases passed in by the ModificationLogic method isn't empty then it will iterate through the
// list and increase their ranks by 1 or null out the rank if it is not within the acceptable limits (1-10).
if (!cases.isEmpty()) {
for (Case c : cases) {
if (c.Case_Rank__c >= 1 && c.Case_Rank__c < 10) {
c.Case_Rank__c = c.Case_Rank__c + 1;
} else {
c.Case_Rank__c = null;
}
}
update cases;
}
return;
}
}
Trigger Handler Test Code -
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/24/15
* @brief: This is the test class for the CaseRankTriggerHandler class
***************************************************************************************/
@isTest
public with sharing class CaseRankTriggerHandlerTest {
// class level variables
static User testRequestor = createTestRequestor();
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/24/15
* @brief: The InsertCase_NewRankNull test method ensures that the insert functionality of the
* CaseRankTrigger is working as intended when a new Case is inserted with a null rank.
***************************************************************************************/
@isTest
static void InsertCase_NewRankNull() {
// creates the initial case load for 'Test Developer' by passing in a list of integers that will become the ranks for the cases
createDeveloperCase_Multiple(new List<Integer> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
// starting the test by inserting a new Case with a null rank
Test.startTest();
createDeveloperCase_Single('Null', null);
Test.stopTest();
// queries the system to create a map of Cases assigned to 'Test Developer' that are keyed by Rank with Subject as the value
Map<Decimal, String> caseMap = createCaseMap();
// system asserts to ensure that Cases are in the proper order
System.assertEquals('Test Case (1)', caseMap.get(1), 'Test Developer should have \'Test Case (1)\' as rank 1 but instead has ' + caseMap.get(1));
System.assertEquals('Test Case (2)', caseMap.get(2), 'Test Developer should have \'Test Case (2)\' as rank 2 but instead has ' + caseMap.get(2));
System.assertEquals('Test Case (3)', caseMap.get(3), 'Test Developer should have \'Test Case (3)\' as rank 3 but instead has ' + caseMap.get(3));
System.assertEquals('Test Case (4)', caseMap.get(4), 'Test Developer should have \'Test Case (4)\' as rank 4 but instead has ' + caseMap.get(4));
System.assertEquals('Test Case (5)', caseMap.get(5), 'Test Developer should have \'Test Case (5)\' as rank 5 but instead has ' + caseMap.get(5));
System.assertEquals('Test Case (6)', caseMap.get(6), 'Test Developer should have \'Test Case (6)\' as rank 6 but instead has ' + caseMap.get(6));
System.assertEquals('Test Case (7)', caseMap.get(7), 'Test Developer should have \'Test Case (7)\' as rank 7 but instead has ' + caseMap.get(7));
System.assertEquals('Test Case (8)', caseMap.get(8), 'Test Developer should have \'Test Case (8)\' as rank 8 but instead has ' + caseMap.get(8));
System.assertEquals('Test Case (9)', caseMap.get(9), 'Test Developer should have \'Test Case (9)\' as rank 9 but instead has ' + caseMap.get(9));
System.assertEquals('Test Case (10)', caseMap.get(10), 'Test Developer should have \'Test Case (10)\' as rank 10 but instead has ' + caseMap.get(10));
System.assertEquals('Test Case (Null)', caseMap.get(null), 'Test Developer should have \'Test Case (Null)\' as rank null but instead has ' + caseMap.get(null));
delete [SELECT Id FROM Case WHERE Resource_Assigned__c = 'Test Developer'];
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/24/15
* @brief: The InsertCase_NewRankNotNull test method ensures that the insert functionality of the
* CaseRankTrigger is working as intended when a new Case is inserted with a rank that is not null.
***************************************************************************************/
@isTest
static void InsertCase_NewRankNotNull() {
// creates the initial case load for 'Test Developer' by passing in a list of integers that will become the ranks for the cases
createDeveloperCase_Multiple(new List<Integer> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
// starting the test by inserting a new Case with a null rank
Test.startTest();
createDeveloperCase_Single('NewNotNull', 4);
Test.stopTest();
// queries the system to create a map of Cases assigned to 'Test Developer' that are keyed by Rank with Subject as the value
Map<Decimal, String> caseMap = createCaseMap();
// system asserts to ensure that Cases are in the proper order
System.assertEquals('Test Case (1)', caseMap.get(1), 'Test Developer should have \'Test Case (1)\' as rank 1 but instead has ' + caseMap.get(1));
System.assertEquals('Test Case (2)', caseMap.get(2), 'Test Developer should have \'Test Case (2)\' as rank 2 but instead has ' + caseMap.get(2));
System.assertEquals('Test Case (3)', caseMap.get(3), 'Test Developer should have \'Test Case (3)\' as rank 3 but instead has ' + caseMap.get(3));
System.assertEquals('Test Case (NewNotNull)', caseMap.get(4), 'Test Developer should have \'Test Case (NewNotNull)\' as rank 4 but instead has ' + caseMap.get(4));
System.assertEquals('Test Case (4)', caseMap.get(5), 'Test Developer should have \'Test Case (4)\' as rank 5 but instead has ' + caseMap.get(5));
System.assertEquals('Test Case (5)', caseMap.get(6), 'Test Developer should have \'Test Case (5)\' as rank 6 but instead has ' + caseMap.get(6));
System.assertEquals('Test Case (6)', caseMap.get(7), 'Test Developer should have \'Test Case (6)\' as rank 7 but instead has ' + caseMap.get(7));
System.assertEquals('Test Case (7)', caseMap.get(8), 'Test Developer should have \'Test Case (7)\' as rank 8 but instead has ' + caseMap.get(8));
System.assertEquals('Test Case (8)', caseMap.get(9), 'Test Developer should have \'Test Case (8)\' as rank 9 but instead has ' + caseMap.get(9));
System.assertEquals('Test Case (9)', caseMap.get(10), 'Test Developer should have \'Test Case (9)\' as rank 10 but instead has ' + caseMap.get(10));
System.assertEquals('Test Case (10)', caseMap.get(null), 'Test Developer should have \'Test Case (10)\' as rank null but instead has ' + caseMap.get(null));
delete [SELECT Id FROM Case WHERE Resource_Assigned__c = 'Test Developer'];
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/24/15
* @brief: The createCaseMap method queries all the developers Cases then creates a map
* keyed by Rank with the Subject as the value. This map will be used to ensure that
* the Cases are in the proper order after any DML has been performed on a Case.
* @return: Map<Decimal, String>
***************************************************************************************/
static Map<Decimal, String> createCaseMap() {
List<Case> caseList = [SELECT Case_Rank__c, Subject FROM Case WHERE Resource_Assigned__c = 'Test Developer' ORDER BY Case_Rank__c];
Map<Decimal, String> caseMap = new Map<Decimal, String>();
for (Case c : caseList) {
caseMap.put(c.Case_Rank__c, c.Subject);
}
return caseMap;
}
/***************************************************************************************
* TEST DATA SECTION - Refactor out of test class after creating Test Data Factory
***************************************************************************************/
static User createTestRequestor() {
Profile testProfile = [SELECT Id from Profile where Name = 'Standard User'];
User requestor = new User(FirstName = 'Test', LastName = 'Requestor', Alias = 'Test.Req', Email = 'newtestrequestor@null.com', UserName = 'newtestrequestor@null.com', ProfileId = testProfile.Id,
TimeZoneSidKey = 'America/Los_Angeles', LocaleSidKey = 'en_US', EmailEncodingKey = 'UTF-8', LanguageLocaleKey = 'en_US');
insert requestor;
return requestor;
}
static List<Case> createDeveloperCase_Multiple(List<Integer> ranks) {
List<Case> developerCaseLoad = new List<Case>();
Case developerCase;
Integer count = 0;
for (Integer rank : ranks) {
count++;
developerCase = new Case(Subject = 'Test Case (' + count + ')', Service_Request_Type__c = 'Development', Requestor__c = testRequestor.Id, Description = 'Foo', Business_Value_of_Change__c = 'Bar',
Business_Area__c = 'Warranty', Requested_Implementation_Date__c = Date.today(), Resource_Assigned__c = 'Test Developer', Resource_Assigned_Email__c = 'newtestdeveloper@null.com', Case_Rank__c = rank);
developerCaseLoad.add(developerCase);
}
for (Case c : developerCaseLoad) {
}
upsert developerCaseLoad;
return developerCaseLoad;
}
static Case createDeveloperCase_Single(String name, Integer rank) {
Case developerCase = new Case(Subject = 'Test Case (' + name + ')', Service_Request_Type__c = 'Development', Requestor__c = testRequestor.Id, Description = 'Foo', Business_Value_of_Change__c = 'Bar',
Business_Area__c = 'Warranty', Requested_Implementation_Date__c = Date.today(), Resource_Assigned__c = 'Test Developer', Case_Rank__c = rank);
upsert developerCase;
return developerCase;
}
}
Workflow Code - I didn't write this one, but click to see pic
CASE( Resource_Assigned__c ,
"Kimberly REDACTED","foo@bar.com",
"Josh REDACTED","foo@bar.com",
"Robert REDACTED","foo@bar.com",
"Jose REDACTED","foo@bar.com",
"Ryan REDACTED","foo@bar.com",
"Lloyd REDACTED","foo@bar.com",
"Nathan REDACTED","foo@bar.com",
"Amber REDACTED","foo@bar.com",
"Ora REDACTED","foo@bar.com",
"Jason REDACTED","foo@bar.com",
"Shalini REDACTED","foo@bar.com",
"Siva REDACTED","foo@bar.com",
"Quinn REDACTED","foo@bar.com",
"Adrienne REDACTED","foo@bar.com",
"Vasantha REDACTED","foo@bar.com",
"Michael REDACTED","foo@bar.com",
"Sudheera REDACTED","foo@bar.com",
"Test Developer","newtestdeveloper@null.com",
"false")
非常感谢你们能给我的任何帮助!
此致,
迈克尔
这是我解决问题的方法。
我重构了测试并删除了两个测试开发人员的创建。相反,我抓取了我们使用的下拉列表中包含的两个随机开发人员,然后我在测试中使用了这些开发人员。
由于所有设置的方式,我不需要使用生产数据 (SeeAllData=true) 来使此修复工作,并且在更改代码后我再也没有遇到测试问题。
您好!
我正在 Salesforce 中对触发器进行单元测试,我一直遇到一个我似乎无法解决的错误,所以我希望有更多经验的人可以帮助我回到正轨。我在 Google 上四处寻找并多次弄乱我的代码结构,但我找不到解决方案。
用途:
我的任务是编写一个触发器来处理维护每个开发人员的案例排名所需的逻辑。每个开发人员都分配有案例,这些案例可能有也可能没有由业务确定的优先级。每个开发人员一次只能优先处理 10 个案例。任何其他情况在排名字段中只会有一个空值。如果插入、更新或删除具有排名的案例,则分配给具有排名的该开发人员的所有其他案例必须相应地自动更新。任何排名高于 10 的案例都将被剔除。
问题:
我已经完成了触发器和触发器处理程序 class 现在我正在编写单元测试以涵盖所有单元测试。当我完成我的第一个单元测试方法时,我收到一个引用工作流问题的错误。我发现并纠正了这个问题,但在我完成我的第二个单元测试方法后,我再次遇到同样的错误。我可以注释掉这两种方法中的任何一种和其他方法,但是每当我 运行 将它们放在一起时,我会在第一个方法上通过,而在第二个方法上失败,并出现相同的原始错误。
代码:
Trigger Code -
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/09/15
* @brief: This is a trigger for the Case object that will modify the rank of the Cases
* assigned to the developer based on a priority set by the Administrator.
***************************************************************************************/
trigger CaseRankTrigger on Case (before insert, before update, before delete) {
// trigger level variables
private static Boolean firstRun = true;
private RecordType ticketRecordType = [SELECT Id FROM RecordType WHERE SobjectType = 'Case' AND Name = 'Salesforce Service Ticket' LIMIT 1];
private List<Case> newTrigger = trigger.new;
private List<Case> currentTrigger = trigger.old;
private List<Case> qualifiedNewCases = new List<Case>();
private List<Case> qualifiedCurrentCases = new List<Case>();
// makes sure that the trigger only runs once
if (firstRun) {
firstRun = false;
// populate trigger Case lists
qualifyCases();
if (qualifiedNewCases.size() == 1 || qualifiedCurrentCases.size() == 1) {
// the CaseRankTriggerHandler constructor method takes (List<Case>, List<Case>, String)
if (trigger.isInsert) CaseRankTriggerHandler handler = new CaseRankTriggerHandler(qualifiedNewCases, qualifiedCurrentCases, 'Insert'); // if a new Case is being inserted into the database
if (trigger.isUpdate) CaseRankTriggerHandler handler = new CaseRankTriggerHandler(qualifiedNewCases, qualifiedCurrentCases, 'Update'); // if an existing Case is being updated
if (trigger.isDelete) CaseRankTriggerHandler handler = new CaseRankTriggerHandler(qualifiedNewCases, qualifiedCurrentCases, 'Delete'); // if an existing Case is deleted from the database
}
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/24/15
* @brief: The qualifyCases method populates a list of Cases for each trigger
* that are of the Salesforce Service Ticket record type only.
* @return: Void
***************************************************************************************/
public void qualifyCases() {
if (newTrigger != null ) {
for (Case c : newTrigger) {
if (c.recordTypeId == ticketRecordType.Id) {
qualifiedNewCases.add(c);
}
}
}
if (currentTrigger != null) {
for (Case c : currentTrigger) {
if (c.recordTypeId == ticketRecordType.Id) {
qualifiedCurrentCases.add(c);
}
}
}
}
}
Trigger Handler Code -
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/09/15
* @brief: This is a Case object trigger handler class that provides logic to the CaseRankTrigger for manipulating
* the ranks of all Cases assigned to a developer based on a priority that is set by an Administrator.
***************************************************************************************/
public with sharing class CaseRankTriggerHandler {
// class level variables
private static Boolean firstRun = true;
private static Boolean modify = false;
private static Integer MAX = 10;
private static Integer MIN = 1;
private List<Case> newTrigger {get; set;}
private List<Case> currentTrigger {get; set;}
private List<Case> cases {get; set;}
private List<Case> newList {get; set;}
private List<Case> currentList {get; set;}
private String developer {get; set;}
private Decimal newRank {get; set;}
private Decimal currentRank {get; set;}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/16/15
* @brief: Class constructor method.
* @return: Void
***************************************************************************************/
public CaseRankTriggerHandler(List<Case> newT, List<Case> oldT, String type) {
if (firstRun) { // makes sure that the trigger only runs once
firstRun = false;
InitializeTrigger(newT, oldT, type); // initializes the trigger
if (developer != null) { // skips trigger if DML is performed on a Case with no developer assigned
ModificationCheck(type); // determines if Cases need to be modified
if (modify) ModificationLogic(type); // modifies Cases if needed
}
}
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/16/15
* @brief: The InitializeTrigger method initializes the handler class based on the type of trigger fired.
* @return: Void
***************************************************************************************/
private void InitializeTrigger(List<Case> newT, List<Case> oldT, String type) {
if (type == 'Insert') {
this.newTrigger = newT;
this.developer = newTrigger[0].Resource_Assigned__c;
this.newRank = newTrigger[0].Case_Rank__c;
this.newList = [SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :developer AND Case_Rank__c != null AND Case_Rank__c = :newRank ORDER BY Case_Rank__c];
} else if (type == 'Update') {
this.newTrigger = newT;
this.currentTrigger = oldT;
this.developer = newTrigger[0].Resource_Assigned__c;
this.newRank = newTrigger[0].Case_Rank__c;
this.currentRank = currentTrigger[0].Case_Rank__c;
this.newList = [SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :developer AND Case_Rank__c != null AND Case_Rank__c = :newRank ORDER BY Case_Rank__c];
this.currentList = [SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :developer AND Case_Rank__c != null AND Case_Rank__c = :currentRank ORDER BY Case_Rank__c];
} else if (type == 'Delete') {
this.currentTrigger = oldT;
this.developer = currentTrigger[0].Resource_Assigned__c;
this.currentRank = currentTrigger[0].Case_Rank__c;
}
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/16/15
* @brief: The ModificationCheck method ensures various conditions are met, depending on the type
* of trigger that was fired, before modifying the ranks of the Cases assigned to the developer.
* @return: Void
***************************************************************************************/
private void ModificationCheck(String type) {
if (type == 'Insert') {
// the Case being inserted has a new rank not equal to null and if the assigned developer already has a Case with the
// same rank as the new rank, we will proceed to modification, if not the record will be inserted without modification.
if (newRank != null && !newList.isEmpty()) {
modify = true;
}
} else if (type == 'Update') {
// if the Case being updated has ranks with different values in both triggers we will proceed to the next check, if not the record is updated without modification.
if (newRank != currentRank) {
// if the Case being updated has a (new rank equal to null and a current rank not equal to 10) or
// if the Case being updated has a new rank not equal to null, we will proceed to the next check,
// if not the record is updated without modification.
if ((newRank == null && currentRank != 10) || newRank != null) {
// if the assigned developer on the Case being updated already has a Case with the same rank as the new or current rank, we will proceed to modification,
// if not the record is updated without modification.
if (!newList.isEmpty() || !currentList.isEmpty()) {
modify = true;
}
}
}
} else if (type == 'Delete') {
// if the Case being deleted has current rank not equal to null, we will proceed to modification, if not the record is deleted without modification.
if (currentRank != null) {
modify = true;
}
}
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/16/15
* @brief: If a Case rank needs to be updated the ModificationLogic method calls the appropriate
* computation method based on trigger type and the values of newRank and currentRank.
* @return: Void
***************************************************************************************/
private void ModificationLogic(String type) {
if (type == 'Insert') {
for (Case c : newTrigger) {
// calls the IncreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank greater than or equal to the new rank.
IncreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND Case_Rank__c >= :newRank ORDER BY Case_Rank__c]);
}
} else if (type == 'Update') {
for (Case c : newTrigger) {
if (currentRank == null) {
// if the current rank is null - calls the IncreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank greater than or equal to the new rank.
IncreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND Case_Rank__c >= :newRank ORDER BY Case_Rank__c]);
} else if (newRank == null) {
// if the new rank is null - calls the DecreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank greater than the current rank.
DecreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND Case_Rank__c > :currentRank ORDER BY Case_Rank__c]);
} else if (newRank > currentRank) {
// if the new rank is greater than the current rank - calls the DecreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank less than or equal to the new rank and greater than to the current rank.
DecreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND (Case_Rank__c <= :newRank AND Case_Rank__c > :currentRank) ORDER BY Case_Rank__c]);
} else if (newRank < currentRank) {
// if the new rank is less than the current rank - calls the IncreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank a.
IncreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND (Case_Rank__c >= :newRank AND Case_Rank__c < :currentRank) ORDER BY Case_Rank__c]);
}
}
} else if (type == 'Delete') {
for (Case c : currentTrigger) {
// calls the DecreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank greater than the current rank.
DecreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :currentTrigger AND Resource_Assigned__c = :developer AND Case_Rank__c > :currentRank ORDER BY Case_Rank__c]);
}
}
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/16/15
* @brief: The DecreaseCaseRank method provides the logic required to properly
* decrease or null out the ranks of the Cases assigned the the developer.
* @return: Void
***************************************************************************************/
private void DecreaseCaseRank(List<Case> cases) {
// if the list of Cases passed in by the ModificationLogic method isn't empty then it will iterate through the
// list and decrease their ranks by 1 or null out the rank if it is not within the acceptable limits (1-10).
if (!cases.isEmpty()) {
for (Case c : cases) {
if (c.Case_Rank__c >= 1 && c.Case_Rank__c <= 10) {
c.Case_Rank__c = c.Case_Rank__c - 1;
} else {
c.Case_Rank__c = null;
}
}
update cases;
}
return;
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/16/15
* @brief: The IncreaseCaseRank method provides the logic required to properly
* increase or null out the ranks of the Cases assigned the the developer.
* @return: Void
***************************************************************************************/
private void IncreaseCaseRank(List<Case> cases) {
// if the list of Cases passed in by the ModificationLogic method isn't empty then it will iterate through the
// list and increase their ranks by 1 or null out the rank if it is not within the acceptable limits (1-10).
if (!cases.isEmpty()) {
for (Case c : cases) {
if (c.Case_Rank__c >= 1 && c.Case_Rank__c < 10) {
c.Case_Rank__c = c.Case_Rank__c + 1;
} else {
c.Case_Rank__c = null;
}
}
update cases;
}
return;
}
}
Trigger Handler Test Code -
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/24/15
* @brief: This is the test class for the CaseRankTriggerHandler class
***************************************************************************************/
@isTest
public with sharing class CaseRankTriggerHandlerTest {
// class level variables
static User testRequestor = createTestRequestor();
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/24/15
* @brief: The InsertCase_NewRankNull test method ensures that the insert functionality of the
* CaseRankTrigger is working as intended when a new Case is inserted with a null rank.
***************************************************************************************/
@isTest
static void InsertCase_NewRankNull() {
// creates the initial case load for 'Test Developer' by passing in a list of integers that will become the ranks for the cases
createDeveloperCase_Multiple(new List<Integer> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
// starting the test by inserting a new Case with a null rank
Test.startTest();
createDeveloperCase_Single('Null', null);
Test.stopTest();
// queries the system to create a map of Cases assigned to 'Test Developer' that are keyed by Rank with Subject as the value
Map<Decimal, String> caseMap = createCaseMap();
// system asserts to ensure that Cases are in the proper order
System.assertEquals('Test Case (1)', caseMap.get(1), 'Test Developer should have \'Test Case (1)\' as rank 1 but instead has ' + caseMap.get(1));
System.assertEquals('Test Case (2)', caseMap.get(2), 'Test Developer should have \'Test Case (2)\' as rank 2 but instead has ' + caseMap.get(2));
System.assertEquals('Test Case (3)', caseMap.get(3), 'Test Developer should have \'Test Case (3)\' as rank 3 but instead has ' + caseMap.get(3));
System.assertEquals('Test Case (4)', caseMap.get(4), 'Test Developer should have \'Test Case (4)\' as rank 4 but instead has ' + caseMap.get(4));
System.assertEquals('Test Case (5)', caseMap.get(5), 'Test Developer should have \'Test Case (5)\' as rank 5 but instead has ' + caseMap.get(5));
System.assertEquals('Test Case (6)', caseMap.get(6), 'Test Developer should have \'Test Case (6)\' as rank 6 but instead has ' + caseMap.get(6));
System.assertEquals('Test Case (7)', caseMap.get(7), 'Test Developer should have \'Test Case (7)\' as rank 7 but instead has ' + caseMap.get(7));
System.assertEquals('Test Case (8)', caseMap.get(8), 'Test Developer should have \'Test Case (8)\' as rank 8 but instead has ' + caseMap.get(8));
System.assertEquals('Test Case (9)', caseMap.get(9), 'Test Developer should have \'Test Case (9)\' as rank 9 but instead has ' + caseMap.get(9));
System.assertEquals('Test Case (10)', caseMap.get(10), 'Test Developer should have \'Test Case (10)\' as rank 10 but instead has ' + caseMap.get(10));
System.assertEquals('Test Case (Null)', caseMap.get(null), 'Test Developer should have \'Test Case (Null)\' as rank null but instead has ' + caseMap.get(null));
delete [SELECT Id FROM Case WHERE Resource_Assigned__c = 'Test Developer'];
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/24/15
* @brief: The InsertCase_NewRankNotNull test method ensures that the insert functionality of the
* CaseRankTrigger is working as intended when a new Case is inserted with a rank that is not null.
***************************************************************************************/
@isTest
static void InsertCase_NewRankNotNull() {
// creates the initial case load for 'Test Developer' by passing in a list of integers that will become the ranks for the cases
createDeveloperCase_Multiple(new List<Integer> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
// starting the test by inserting a new Case with a null rank
Test.startTest();
createDeveloperCase_Single('NewNotNull', 4);
Test.stopTest();
// queries the system to create a map of Cases assigned to 'Test Developer' that are keyed by Rank with Subject as the value
Map<Decimal, String> caseMap = createCaseMap();
// system asserts to ensure that Cases are in the proper order
System.assertEquals('Test Case (1)', caseMap.get(1), 'Test Developer should have \'Test Case (1)\' as rank 1 but instead has ' + caseMap.get(1));
System.assertEquals('Test Case (2)', caseMap.get(2), 'Test Developer should have \'Test Case (2)\' as rank 2 but instead has ' + caseMap.get(2));
System.assertEquals('Test Case (3)', caseMap.get(3), 'Test Developer should have \'Test Case (3)\' as rank 3 but instead has ' + caseMap.get(3));
System.assertEquals('Test Case (NewNotNull)', caseMap.get(4), 'Test Developer should have \'Test Case (NewNotNull)\' as rank 4 but instead has ' + caseMap.get(4));
System.assertEquals('Test Case (4)', caseMap.get(5), 'Test Developer should have \'Test Case (4)\' as rank 5 but instead has ' + caseMap.get(5));
System.assertEquals('Test Case (5)', caseMap.get(6), 'Test Developer should have \'Test Case (5)\' as rank 6 but instead has ' + caseMap.get(6));
System.assertEquals('Test Case (6)', caseMap.get(7), 'Test Developer should have \'Test Case (6)\' as rank 7 but instead has ' + caseMap.get(7));
System.assertEquals('Test Case (7)', caseMap.get(8), 'Test Developer should have \'Test Case (7)\' as rank 8 but instead has ' + caseMap.get(8));
System.assertEquals('Test Case (8)', caseMap.get(9), 'Test Developer should have \'Test Case (8)\' as rank 9 but instead has ' + caseMap.get(9));
System.assertEquals('Test Case (9)', caseMap.get(10), 'Test Developer should have \'Test Case (9)\' as rank 10 but instead has ' + caseMap.get(10));
System.assertEquals('Test Case (10)', caseMap.get(null), 'Test Developer should have \'Test Case (10)\' as rank null but instead has ' + caseMap.get(null));
delete [SELECT Id FROM Case WHERE Resource_Assigned__c = 'Test Developer'];
}
/***************************************************************************************
* @author: Michael *REDACTED*
* @email: michael.*REDACTED*@*REDACTED*.com
* @date: 11/24/15
* @brief: The createCaseMap method queries all the developers Cases then creates a map
* keyed by Rank with the Subject as the value. This map will be used to ensure that
* the Cases are in the proper order after any DML has been performed on a Case.
* @return: Map<Decimal, String>
***************************************************************************************/
static Map<Decimal, String> createCaseMap() {
List<Case> caseList = [SELECT Case_Rank__c, Subject FROM Case WHERE Resource_Assigned__c = 'Test Developer' ORDER BY Case_Rank__c];
Map<Decimal, String> caseMap = new Map<Decimal, String>();
for (Case c : caseList) {
caseMap.put(c.Case_Rank__c, c.Subject);
}
return caseMap;
}
/***************************************************************************************
* TEST DATA SECTION - Refactor out of test class after creating Test Data Factory
***************************************************************************************/
static User createTestRequestor() {
Profile testProfile = [SELECT Id from Profile where Name = 'Standard User'];
User requestor = new User(FirstName = 'Test', LastName = 'Requestor', Alias = 'Test.Req', Email = 'newtestrequestor@null.com', UserName = 'newtestrequestor@null.com', ProfileId = testProfile.Id,
TimeZoneSidKey = 'America/Los_Angeles', LocaleSidKey = 'en_US', EmailEncodingKey = 'UTF-8', LanguageLocaleKey = 'en_US');
insert requestor;
return requestor;
}
static List<Case> createDeveloperCase_Multiple(List<Integer> ranks) {
List<Case> developerCaseLoad = new List<Case>();
Case developerCase;
Integer count = 0;
for (Integer rank : ranks) {
count++;
developerCase = new Case(Subject = 'Test Case (' + count + ')', Service_Request_Type__c = 'Development', Requestor__c = testRequestor.Id, Description = 'Foo', Business_Value_of_Change__c = 'Bar',
Business_Area__c = 'Warranty', Requested_Implementation_Date__c = Date.today(), Resource_Assigned__c = 'Test Developer', Resource_Assigned_Email__c = 'newtestdeveloper@null.com', Case_Rank__c = rank);
developerCaseLoad.add(developerCase);
}
for (Case c : developerCaseLoad) {
}
upsert developerCaseLoad;
return developerCaseLoad;
}
static Case createDeveloperCase_Single(String name, Integer rank) {
Case developerCase = new Case(Subject = 'Test Case (' + name + ')', Service_Request_Type__c = 'Development', Requestor__c = testRequestor.Id, Description = 'Foo', Business_Value_of_Change__c = 'Bar',
Business_Area__c = 'Warranty', Requested_Implementation_Date__c = Date.today(), Resource_Assigned__c = 'Test Developer', Case_Rank__c = rank);
upsert developerCase;
return developerCase;
}
}
Workflow Code - I didn't write this one, but click to see pic
CASE( Resource_Assigned__c ,
"Kimberly REDACTED","foo@bar.com",
"Josh REDACTED","foo@bar.com",
"Robert REDACTED","foo@bar.com",
"Jose REDACTED","foo@bar.com",
"Ryan REDACTED","foo@bar.com",
"Lloyd REDACTED","foo@bar.com",
"Nathan REDACTED","foo@bar.com",
"Amber REDACTED","foo@bar.com",
"Ora REDACTED","foo@bar.com",
"Jason REDACTED","foo@bar.com",
"Shalini REDACTED","foo@bar.com",
"Siva REDACTED","foo@bar.com",
"Quinn REDACTED","foo@bar.com",
"Adrienne REDACTED","foo@bar.com",
"Vasantha REDACTED","foo@bar.com",
"Michael REDACTED","foo@bar.com",
"Sudheera REDACTED","foo@bar.com",
"Test Developer","newtestdeveloper@null.com",
"false")
非常感谢你们能给我的任何帮助!
此致,
迈克尔
这是我解决问题的方法。
我重构了测试并删除了两个测试开发人员的创建。相反,我抓取了我们使用的下拉列表中包含的两个随机开发人员,然后我在测试中使用了这些开发人员。
由于所有设置的方式,我不需要使用生产数据 (SeeAllData=true) 来使此修复工作,并且在更改代码后我再也没有遇到测试问题。