14

I have an Apex Class, where 55% of the code is inside a catch, requiring a DML exception to be executed. Since the DML update request is using data from an SOQL request inside this class, I have no idea how I can trigger a DML exception.

Is it possible to lock a certain record from updating (or all the database), in a way that could trigger a DML exception?

Thanks.

12

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.

  • Do you have an example of that? I honestly have no idea how to implement what you are telling me. Are you suggesting to add some additional code in my Apex Class to detect if it's running for a test? That's a little bit weird to add some test script inside an Apex class. – jpmonette Mar 21 '13 at 20:48
  • @jpmonette How you would get it to fail depends on exactly what the method you are trying to test is doing. Are you able to put a basic outline of the method into the question? – Daniel Ballinger Mar 21 '13 at 20:57
  • Well, if the database state become read-only for a certain reason, the class could do the SOQL request, but not a DML operation, triggering the DML Exception. I triend with Test.setReadOnlyApplicationMode(true);, but this is triggering a distinct DML Exception, so it is not working... I guess I won't have a choice but to modify my Apex Class and input something similar to what you showed me. This is breaking the whole idea of seperating a Unit test thought =/ – jpmonette Mar 21 '13 at 21:15
  • Putting the test within the Apex class definition does give you some more options for manipulating the classes state/members. But yes, sometimes the simplest option is to directly manipulate the method. Less than ideal, but it is hard to offer other options without seeing the method in question. – Daniel Ballinger Mar 21 '13 at 21:30
6

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
    }
}
  • 2
    Nice solution, easy enough to cause a DML Exception with a blank Account object. Another suggestion may be to have all the exceptions go through an Exception handler class that can be tested independently. Then change the core method to call the Exception handler. You the above code to throw the exception so you can get 100% coverage! – andyj24 Mar 29 '13 at 18:16
1

Do you have a validation rule, uniqueness constraint or something similar that you can break in normal way? I mean "while testing your actual logic" as opposed to "just coming up with a crazy scenario to get code coverage". Although I prefer to not write unit tests that rely too hard on parts that can be edited on production server without deployment...

Having said that - one of easiest methods I know is to pass an invalid id. It'll be plausible enough to pass the compilation and fail in runtime.

Contact c = new Contact(FirstName = 'Unit', LastName = 'Test', 
    AccountId = UserInfo.getUserId()
);
insert c;

System.DmlException: Insert failed. First exception on row 0; first error:
FIELD_INTEGRITY_EXCEPTION, Account ID: id value of incorrect type: (value redacted)

A slight modification would be to pass a valid object id but one that's invisible to current user (for example because of sharing settings). You'd need System.runAs() to pull this one off though because normally test code runs under SysAdmin priviledges.

0

One possibility is to add an invalid element to the list you are updating, only if it's a test run. So instead of just updating a single SObject, add that SObject to a list and then add the invalid SObject during test executions to throw a DML exception for you.

For example:

if(System.Test.isRunningTest())
   itemList.add(new Contact());
0

I used the answer provided by @eyescream and modified it a bit. I set the OwnerId to a user that is inactive.

i.e.

  My_Object__c myRecord = new My_Object__c()
  myRecord.OwnerId = '005xxxxxxxxx'; // id of user that is inactive
  insert myRecord;

This results an a DmlException:

Insert failed. First exception on row 0; first error: INACTIVE_OWNER_OR_USER, operation performed with inactive user [005xxxxxxxxxxx] as owner of 01Ixxxxxxxxxx: []

In my scenario, I have a VF extension that upon save, has a try/catch block. By setting the OwnerId to an inactive user, I can force the record to throw an exception and get code coverage/validation of the try/catch block.

i.e.

My_Object__c myRecord = new My_Object__c();    
MyExtension myExt = new MyExtension (new ApexPages.StandardController(myRecord));

myExt.theRecord.OwnerId = '005xxxxxxxxx'; // id of user that is inactive

PageReference retPage = myExt.mySaveMethod(); // this will return the page reference set in the catch block

A hard coded Id may not work in all environments (e.g. Sandboxes). This can be extended to get the Id of a User that is in the environment you're currently in via a SOQL query.

What I like about this is it should work for every object (except objects that are the detail in a master/detail relationship).

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.