-1

For this class below, which essentially counts the number of Open cases on an Account, on update/insert/delete of a Case, can anyone help me write/provide a scenario of a test method that can hit the condition where there are any failures for the Database.update to the list of accounts ? I'm not able to figure out a valid scenario where this situation can occur in test class ?

public static void caseTriggerHandler(List<Case> newListOfCases)
{
    if(newListOfCases != NULL && !newListOfCases.isEmpty()){
        Set<Id> setRelatedAcctIds = new Set<Id>();
        for (Case c: newListOfCases ) {
            if(c.AccountId != null)
                setRelatedAcctIds.add(c.AccountId);
        }
        List<Account> lstRelatedAccts = [Select Id, Name, (Select Id from Cases where status <> 'Closed') 
                                        from Account WHERE Id in: setRelatedAcctIds ];
        for (Account a: lstRelatedAccts ) {
            List<Case> lstCasesOfAccount = a.Cases;
            a.Number_of_Open_Cases__c = lstCasesOfAccount.size();
        }

        try{
            Integer count = 0;
            Database.SaveResult[] srList = Database.update(lstRelatedAccts, false);
            for (Database.SaveResult sr : srList) {
                 if (!sr.isSuccess()) 
                      for(Database.Error err : sr.getErrors()){
                          System.debug('err.getMessage());
                          // Do something else
                          // lstRelatedAccts[count].adderror('A custom error' + err.getMessage());
                      }
                count++;
            }             
        } catch(Exception e){
            System.debug(e.getMessage());
            // Do something else
        } finally {

        }
    }

}

The below test method handles the positive scenario. I'm not able to come up with a test method that might throw a DML error on Account or even the Catch block inside the handler.

public testMethod static void caseInsertion() {

    Account acct = new Account(name='test account');
    insert acct;

    Case c = new Case(AccountId=acct.Id, Status='Open');
    insert c;
 }

On a different note, the relationship SOQL in the code has a govr limit on the number of child/related records upto 1000. Is there a better way to handle this to get this working for say, an Account with 1500 cases ?

  • What about undelete? If you can't figure out how to cover something, it means you need to break your method into smaller chunks. – Adrian Larson Oct 20 '18 at 21:13
  • 1
    Please do not destroy question content. This question has received a quality answer that provides value to the community. Destroying the content of the original question diminishes that value. I have locked this question to prevent further edit battles. – David Reed May 20 at 18:11
5

100% Coverage Is Cake

You can't always have your cake and eat it, too. Sometimes, you just have to accept that you might not be able to generate an error. You have two basic choices: either get 100% coverage by introducing test logic into your production code, thus sacrificing real performance for "feel-good" metrics, or get better performance and deal with having "only" 90% coverage (which is really a good target metric for realistic code).

If you can't generate an error, that's fine. Sometimes it's just not possible. My general rule of thumb is that covering error handling is strictly optional unless code coverage is too low. You already know what's going to happen when you enter a try-catch block, so you don't necessarily need to validate this every single time.

You Can Mock The Class

If you really want to, you can build a mock of the class. Keep in mind that if you do this, you'll need to separate your error handling into a separate utility class, and it'll complicate your code tremendously just to get the last few percentage.

Make Sure You Need That Code

You don't need the try-catch block, nor the initial null/empty list checks, nor the finally block. Trimming unnecessary logic can improve performance and generally improve code coverage.


Limited Sub-Query Rows

The system ends up generating a QueryLocator when a sub-query result is too big, usually between 200 to 1000 rows of data, depending on the number and type of columns selected. The real problem is you're querying individual records, but you really only care about the aggregate. This means you need an aggregate query. The following version of your trigger logic is all you need:

public static void caseTriggerHandler(List<Case> newListOfCases) {
    Set<Id> accountIds = new Set<Id>();
    for(Case record: newListOfCases) {
        accountIds.add(record.AccountId);
    }
    accountIds.remove(null);
    Map<Id, AggregateResult> caseCount = new Map<Id, AggregateResult>(
        [SELECT AccountId Id, COUNT(Id) total 
         FROM Case
         WHERE AccountId = :accountIds AND IsClosed = FALSE]
    );
    Account[] updatedAccounts = new Account[0];
    for(Id accountId: accountIds) {
        Account record = new Account(Id=accountId, Number_of_Open_Cases__c=0);
        AggregateResult result = caseCount.get(accountId);
        if(result != null) {
            record.Number_of_Open_Cases__c = (Decimal)result.get('total');
        }
        updatedAccounts.add(record);
    }
    Map<Id, Database.Error> errors = new Map<Id, Database.Error>();
    Database.SaveResult[] results = Database.update(updatedAccounts, false);
    for(Integer i = 0, s = results.size(); i < s; i++) {
        if(!results[i].isSuccess()) {
            errors.put(updatedAccounts[i].Id, results[i].getErrors()[0]);
        }
    }
    for(Case record: newListOfCases) {
        if(errors.containsKey(record.AccountId)) {
            record.addError('Unable to update case because of related error: '+errors.get(record.AccountId).getMessage());
        }
    }
}

Even without generating an error, this trigger logic should hit approximately 90% coverage.


As Adrian Larson mentioned, you really should consider undeletions, and I would add you also need to be concerned about what happens when a case changes accounts (e.g. because it was on the wrong account).


Finally, instead of making your own code, and worrying about coverage, etc, you could just use DLRS, which generates the necessary trigger logic for you and will keep your records in sync. There's no real reason to write your own version when you already have a perfectly good utility already available.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.