Hello!
I am working on unit tests for trigger within Salesforce and I keep encountering an error that I can't seem to solve so I am hoping someone with more experience can help me get back on track. I've hunted around on Google and messed with the structure of my code many times but I'm unable to find a solution.
Purpose:
I have been tasked with writing a trigger that will handle the logic required to maintain case rankings per developer. Each developer is assigned cases and those cases may or may not have a priority determined by the business. Each developer may only have 10 cases prioritized at any one time. Any other cases will just have a null value in the ranking field. If a case with a ranking is inserted, updated, or deleted then all the other cases assigned to that developer with a rank must automatically update accordingly. Any case with a rank higher than 10 will get nulled out.
Problem:
I have completed the trigger and trigger handler class now I am writing unit tests to cover all the unit tests. When I finished my first unit test method I got an error that referenced a workflow issue. I found and corrected the issue but after I finished my second unit test method I get the same error again. I can comment out either of the two methods and the other passes but whenever I run them together I get a pass on the first and a fail on the second with the same original error.
Code:
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 = '[email protected]', UserName = '[email protected]', 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 = '[email protected]', 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","[email protected]",
"Josh REDACTED","[email protected]",
"Robert REDACTED","[email protected]",
"Jose REDACTED","[email protected]",
"Ryan REDACTED","[email protected]",
"Lloyd REDACTED","[email protected]",
"Nathan REDACTED","[email protected]",
"Amber REDACTED","[email protected]",
"Ora REDACTED","[email protected]",
"Jason REDACTED","[email protected]",
"Shalini REDACTED","[email protected]",
"Siva REDACTED","[email protected]",
"Quinn REDACTED","[email protected]",
"Adrienne REDACTED","[email protected]",
"Vasantha REDACTED","[email protected]",
"Michael REDACTED","[email protected]",
"Sudheera REDACTED","[email protected]",
"Test Developer","[email protected]",
"false")
I really appreciate any help you all can give me on this one!
Regards,
Michael