My solution to this, especially for situations where exception catching does rollbacks that I want to verify in my test coverage is as follows:
First, a class FlowControl with static variables:
public without sharing class FlowControl {
public enum ForceFailure {FOO_FAIL,BAR_FAIL}; // add enums as needed
private static Map<ForceFailure,Boolean> forceFailureToEnabledMap = new Map<ForceFailure,Boolean>();
static {resetAll();}
public static boolean isForceFail(ForceFailure executionUnit) {return forceFailureToEnabledMap.get(executionUnit);}
public static void setForceFail(ForceFailure executionUnit) {forceFailureToEnabledMap.put(executionUnit,true);}
public static void resetAll() {
for (ForceFailure ff : ForceFailure.values())
forceFailureToEnabledMap.put(ff,false);
}
}
Then, in my code myFooMethod() where I care about getting test coverage on the catch block:
... code within myFooMethod()
try {
// some code
// just before the DML statement
if (FlowControl.isForceFail(FlowControl.ForceFailure.FOO_FAIL)) {Integer i = 10 / 0;} // divide by zero
update someObj;
}
catch (Exception e) {/* my exception code ... */}
And finally, in the testmethod that tests error handling:
...
FlowControl.setForceFail(FlowControl.ForceFailure.FOO_FAIL);
myFooMethod();
System.assert(/* my assertions to verify catch block did the right thing */ );
The advantages to this are:
(1) I can customize the exception thrown per use case. The example above throws a divide by zero exception because the catch block doesn't care what exception occurs in its catch logic. Your could create bogus ID fields, modify where clauses or force a known SObject validation rule to fail.
(2) I can set up multiple exception 'test triggers' that don't step on each other during a testmethod execution that spans multiple try-catch blocks scattered throughout your code
(3) A reset method to use when simulating within a single testmethod multiple end user interactions separated by page redirects or API events.
(4) You can get to the magic and satisfying 100% code coverage stats.
The disadvantage, of course, was already noted - your PROD codebase has code inserted to assist testcoverage - simple code and benign, but still unnecessary for PROD operations.