0

We have implemented one method using Apex for delete some records under the class which is without sharing.

In the method, first we are retrieving the list of the records(which needs to be deleted) using SOQl and if the list is not null deleting those records using DML.

We have written our code under try catch block.

In test class we are not able to cover the catch block.

Tried with multiple approaches like to cover the catch block- invalid id, null , run as particular user who does not have the access on the object. Still not able to achieve.

Below is the main class code snippet -

@AuraEnabled 
public static void deleteObj(list<Id> Id) {
    list<Error_Logs__c> lstErrorLogs = new list<Error_Logs__c();
    try{
        list< MyObject__c> items =[SELECT id 
                                FROM MyObject__c 
                                WHERE Id IN :Id];
        if (items!=null && !items.isEmpty()){
            delete items;
        }
        
    }catch (Exception e){
        lstErrorLogs.add('Error'));
        
        if(lstErrorLogs.size()>0){
            insert lstErrorLogs;
        }
    }
}

Below is the test class code snippet -

@isTest
public static void testdeleteObj(){
    

        List<MyObject__c> objList = [SELECT id 
                                     FROM MyObject__c];

        Test.startTest();
        Myclass.deleteObj(objList[0].Id);
        Test.stopTest(); 
    
    
    List<MyObject__c> queryList  = [SELECT Id
                                    FROM MyObject__c];
    System.assertEquals(0, queryList.size());


}

Anyone has any idea to cover the catch in the scenario?

1
  • 1
    Move your code out of the catch block into a method, which is @TestVisible. Commented Nov 30, 2023 at 13:02

2 Answers 2

1

You can use the Test.isRunningTest method to check if the code is running in a test context and then throw a custom exception.

For example, you can modify your main class code like this:

@AuraEnabled 
public static void deleteObj(list<Id> listOfIds) {
    list<Error_Logs__c> lstErrorLogs = new list<Error_Logs__c();
    try{
        list< MyObject__c> items =[SELECT Id 
                                   FROM MyObject__c 
                                   WHERE Id IN :listOfIds];
        if(items!=null && !items.isEmpty()){
            delete items;
        }
        
        // Add this line to throw a custom exception in test context
        if(Test.isRunningTest()) throw new MyCustomException('Test exception');
    } catch (Exception e){
        lstErrorLogs.add('Error'));        
        if(lstErrorLogs.size() > 0){
            insert lstErrorLogs;
        }
    }
}
3
  • expedient but not best practice; better to move delete operation into a separate class and then at test time, provide a mock implementation of that class that throws an exception. Putting Test.isRunningTest into PROD code is a code smell
    – cropredy
    Commented Dec 1, 2023 at 19:24
  • @cropredy how do you provide a mock implementation of that class that throws an exception?
    – jojopogi
    Commented Dec 1, 2023 at 22:00
  • @jojopogi - see my answer; a technique well worth learning to deal with situations like this.
    – cropredy
    Commented Dec 2, 2023 at 2:13
0

OK, let's start by simplifying your code

@AuraEnabled 
public static void deleteObj(List<Id> ids) {
    list<Error_Logs__c> lstErrorLogs = new list<Error_Logs__c();
    try{
        delete items =[SELECT Id 
                         FROM MyObject__c 
                         WHERE Id IN :ids];
        }
        
    }catch (Exception e){
        insert new Error_Log__c(Message__c = e.getMessage());       
    }
}

I'm going to ignore (but you should not) that the error is not being passed back to the caller.

Note the delete can be done on the result of the soql. No need for the empty checks.

Now, to test the exception block, you have some choices:

Option 1

In the test method, use a running user that doesn't have permissions to delete MyObject__c (either not CRUD or not the owner/higher in role hierarchy as owner and without Modify All Data)

Option 2

This is a more general solution that is good to have in your toolbag

Step 1 Create an interface

public interface IDeleteWorker {
  void execute(List<Id> ids);
}

Step 2 Create a new class (can also be an inner class)

public class DeleteWorker implements IDeleteWorker {
   public void execute(List<Id> ids) {
      delete items =[SELECT Id 
                         FROM MyObject__c 
                         WHERE Id IN :ids];
   }
}

Step 3

Modify the AuraEnabled class to call the production implementation of IDeleteWorker (default) but allow a testmethod to inject a mock

public class MyClass {   
  @TestVisible IDeleteWorker deleteWorker = new DeleteWorker();
  @AuraEnabled 
  public static void deleteObj(List<Id> ids) {
    list<Error_Logs__c> lstErrorLogs = new list<Error_Logs__c();
    try{
        deleteWorker.execute(ids);
    }catch (Exception e){
        insert new Error_Log__c(Message__c = e.getMessage());       
    }
  }
}

Step 4 In the test method, create a mock version of the DeleteWorker that throws an exception. Then inject that mock before you test the use case for a delete exception.

@IsTest
private class MyTest {
  private class MockExceptionThrowDeleteWorker implements IDeleteWorker {
    void execute(List<Id>) {
      throw new System.IllegalArgumentException('mocked exception');
    }
  }
  @IsTest
  static void givenMockDeleteWorkerThatThrowsExceptionVerifyLogWritten() {
    // given mock worker that throws exception
    IDeleteWorker mockDeleteWorker = new MockExceptionThrowDeleteWorker();
    MyClass.deleteWorker = mockDeleteWorker;
    // when deleteObj
    MyClass.deleteObj(new List<Id>();
    // then verify log written
    Assert.isTrue(![SELECT Id FROM Error_Log__c].isEmpty(), sb error log');
  }
}

Note that the happy path test method will not use a mock IDeleteWorker, thus unit testing the pure Production code path

Option 3

Adopt the Unit of Work pattern for all of your DML (see Salesforce Platform Enterprise Architecture aka fflib)

This is an advanced topic that I can't do justice to here but highly recommended to use in large Apex orgs. The Unit of Work pattern implicitly supports mocking and, in conjunction with the ApexMocks library (GitHub), you can mock a Unit of Work to throw an exception (plus a lot more stuff)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .