Triggering a DML Exception for Test Coverage

You can use FOR UPDATE to lock a certain record, but it would be locked to the testing transaction, so it might not give you the DML Exception you want.

How dependant are you on the content of the DML Exception? If you just want an exception of the correct type use Test.isRunningTest() to deliberately cause the exception.

try {

    // If you can't manipulate the Apex methods input or database state to cause a
    // DMLExcpetion then you can deliberately cause the DMLException to improve your
    // code coverage and check the functionality of the exception handling.
    // You could also use a flag set from your test method to indicate that the 
    // exception should be thrown
    if(Test.isRunningTest()) {
        // Cause DMLException
        insert new Lead();
    }

} catch (DMLException ex) {
    // Existing exception handling.        
}

As you mentioned, it is less than ideal to modify the body of the Apex Method you are testing to facilitate testing. If you can, manipulate either the methods inputs or the state of the database from the test method to cause the DMLException.

You mentioned that the update is using data that is retrieved from a SOQL request in the class. If you put the test method within the class definition you can manipulate the private members directly from the test.


Without seeing the underlying code, it's hard to see whether the approach I'm recommending will work exactly, but in most cases it should be possible to refactor to something more testable like:

public class MyClass
{
    public void myMethod()
    {
        //Get your records from SOQL - let's assume they are accounts
        Account[] accounts = [SELECT CreatedById, CreatedDate, LastModifiedById, LastModifiedDate, BillingCity FROM Account WHERE Name='Acme'];
        //Create new method to process the accounts
        processAccounts(accounts);
    } 

    public void processAccounts( Account[] accounts)
    {  
         try
         {
             //Normal processing here
         }
         catch (DMLException ex) 
         {
             //Handle exception here or in separate method
         }
    }
}

Now in your unit test you can generate a list of accounts that will cause a DML exception when you attempt to insert or update them (for example, create a list of accounts that do not have required fields and pass those to the processAccounts method):

@isTest
private class TestMyClass 
{
    static void testAccountProcessing()
    {
         //Generate accounts that will cause a DML exception
         Account a = new Account(BillingCity='New York');
         Account b = new Account();
         Account[] accounts = new Account[]{a,b};
         MyClass.processAccounts(accounts);
         //Test Assertions here
    }
}