7

I would know how to cover, with the test class, the catch block in my apex trigger:

APEX TRIGGER:

    try{

             update olis_updated;

       } catch (DMLException e){  
              for (OpportunityLineItem o :olis){  
                     o.addError('There was a problem setting Product_Category__c '); 
              }  
        } 

TEST CLASS:

I've tried like following, but it doesn't work

 // GENERATE EXECEPTION
               test.startTest();
                 lineItem1.id=lineItem2.id;
                 update lineItem1;
               test.stopTest();

How can i generate an exeception? Thanks in advantage for any advice.

BR

19

Not all code that is written can be covered. This is a fact of salesforce.com development. There is a reason why the deployment rule is 75% coverage, and not 100% coverage. This "wiggle room" exists so that developers can test the majority of their code, since exception handling can be hard to troubleshoot and even harder to test through scripting, since errors are supposed to be the exception, not the rule. The important thing about your test methods is that they verify your code works correctly. Unless you have a way of crafting a record that will fail, the only other method-- which is strongly not recommended-- would be to write a exception built into the actual code you're testing. This involves using Test.isRunningTest to set up an arbitrary failure.

Sample Code: Original

trigger updateAccounts on Contact (after insert) {
    Map<Id, Account> accounts = new Map<Id, Account>();
    for(Contact record: Trigger.new)
        accounts.put(record.AccountId, null);
    accounts.remove(null);
    accounts.putAll([SELECT Id,Count_Contact__c FROM Account WHERE Id IN :accounts.keySet()]);
    for(Account record: accounts.values())
        if(record.Count_Contact__c == null)
            record.Count_Contact__c = 0;
    for(Contact record: Trigger.new)
        if(accounts.containsKey(record.AccountId))
            accounts.get(record.AccountId).Count_Contact__c++;
    try {
        update accounts.values();
    } catch(Exception e) {
        for(Contact record: Trigger.new)
            record.addError('Failed to update account');
    }
}

Sample Code: Modified

    try {
        if(Test.isRunningTest() && globalFlags.crashTest) {  // Purposely crash test.
            accounts.values()[0].Id = null;
        }
        update accounts.values();
    } catch(Exception e) {
        for(Contact record: Trigger.new)
            record.addError('Failed to update account');
    }

globalFlags would be a class that holds static values for various things. This flag, called crashTest, would be set to true before attempting to update the records. When the trigger sees that it's supposed to crash, it does. You can test your code both ways, once with the flag set, the other with the flag cleared. This will help you cover 100% code.

However, this sort of technique isn't recommended for casual optimization of code coverage, because it introduces test behavior in your production code. Such code can later become the source of mystery failures, such as when a test method starts failing because the code though it should crash, or a bug elsewhere somehow causes the trigger to think it's testing and crashes in production. It also increases testing time by requiring developers to actually navigate to the page/record/etc and manually verify that it doesn't crash after code changes.

Of course, one also has to realize that sometimes you can't avoid testing in production code, although the exceptions to the rule of not mixing test code into production are becoming fewer now as testing improves (such as mock callout responses).

One other strategy that does help, though, is to create a class specifically for handling database errors that can be independently tested.

First, you'd change the logic to not use exception handling:

globalUtils.renderErrors(Database.update(olis_updated, false), Trigger.new);

Next, you'd construct your class and method:

public class globalUtils {
    public static void renderErrors(Database.SaveResult[] results, OpportunityLineItem[] inputs) {
        for(Integer idx = 0; idx < results.size(); idx++)
            if(!results[idx].isSuccess())
               inputs[idx].addError('Failed to update on this record');
    }
}

To test globalUtils, you can forge a test method that will automatically cover the error, such as:

OpportunityLineItem[] src = new OpportunityLineItem[0], dst = new OpportunityLineItem[0];
OpportunityLineItem tst = new OpportunityLineItem();
src.add(tst);
dst.add(tst);
globalUtils.renderErrors(Database.update(src,false),dst);

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.