1

I want to provoke a System.LimitException in a test method, so I can test BatchApexErrorEvent trigger that is supposed to handle uncatchable exceptions like CPU-Timeout etc. However the LimitException terminates the test method as well so I cannot get to the code for verifying the trigger outcome.


Requirement Background

I have a Apex Batch class which performs various actions around permission sets. The batch class has already an exception handling implemented which catches all exception types and performs some cleanup-tasks, like status management on certain custom objects.

public void execute(Database.BatchableContext BC, List<AnalysisRunItem__c> scope) {
    AnalysisRunItem__c item = scope.get(0);
    
    item.Status__c = 'In Progress';
    update item;

    try {
        List<AnalysisRunIssue__c> issues = new PermissionSetAnalyzer(item).findAllIssuesInSet();
        insert issues;
        item.Status__c = 'Checked';
    }
    catch(Exception e) {
        item.ErrorLog__c = e.getTypeName()+' '+e.getMessage()+' '+String.valueOf(System.now());
        item.Status__c = 'Failed';
    }

    update item;
}

In order to handle uncatchable System.LimitExceptions like CPU-Timeout, I am using the Database.RaisesPlatformEvents interface and a BatchApexErrorEvent trigger. The trigger queries the corresponding AsyncApexJob and checks if the apex class matches the expectation and performs the required clean-up tasks. This is done to avoid starting the clean-up tasks for a failure of an unrelated batch class in the org.

    public with sharing class BatchApexErrorEventTriggerHandler {
    
    private List<BatchApexErrorEvent> errorEvents;
    
    // CONSTRUCTOR
    public BatchApexErrorEventTriggerHandler(List<BatchApexErrorEvent> events) {
        this.errorEvents = events;
    }

    // PUBLIC METHOD
    public void execute() {
        Map<Id,AsyncApexJob> asyncJobs = queryJobs();

        List<AnalysisRunItem__c> criticallyFailedItems = identifyItems(asyncJobs);
        update criticallyFailedItems;
    }

    // PRIVATE METHODS
    private Map<Id,AsyncApexJob> queryJobs() {
        Set<Id> jobIds = new Set<Id>();

        for(BatchApexErrorEvent e : errorEvents) {
            jobIds.add(e.AsyncApexJobId);
        }

        return new Map<Id, AsyncApexJob>([SELECT Id, Status, JobType, ApexClass.Name, ApexClass.NamespacePrefix 
                                          FROM AsyncApexJob 
                                          WHERE Id IN :jobIds]);
    }

    private List<AnalysisRunItem__c> identifyItems(Map<Id,AsyncApexJob> asyncJobs) {
        Map<Id, AnalysisRunItem__c> itemMap = new Map<Id, AnalysisRunItem__c>();

        for(BatchApexErrorEvent e : errorEvents) {
            AsyncApexJob job = asyncJobs.get(e.AsyncApexJobId);

            if(job != null && job.ApexClass.Name.equals('AnalyzePermissionSets')) {
                itemMap.putAll( extractItems(e) );
            }            
        }

        return itemMap.values();
    }
}

My problem is writing the test class for the trigger. How can I provoke a System.Limit Exception in a test method without terminating the test method itself? As the "normal" error handling is done in the batch class itself, I have to specifically test a LimitException to achieve code coverage (and a functional test).

The only idea I have is to throw a specific unhandled custom exception in a test context, so the test method can catch the exception and verify the PlatformEvent process. I am not a fan of writing productive code only for testing though, so I would appreciate any other idea that could help.

Thanks in advance

1 Answer 1

1

You don't test it by getting the batch to throw LimitException. You test the handler directly in this case, or service layer if you delegate the logic out. Either way, your test will construct some BatchApexErrorEvent in memory, and then pass those to a logic layer that accepts them as input. Then you test the outputs. Not that different than most unit testing in the end.

2
  • But then the trigger would have 0% Code Coverage since it is never run. Also the Handler relies on an AsyncApexJob record existing to every BatchApexErrorEvent (as is standard behaviour). You could create BatchApexErrorEvent in memory but since the "ApexClass" field on AsyncApexJob is not writeable, I cannot create the job record in memory and somehow pass it to the Handler. Thats why I was hoping to somehow make use of the standard way of testing those events. Dec 22, 2023 at 22:06
  • 1
    You can put in query mocking as a dependency injection and it will simplify your life soooooo much.
    – Adrian Larson
    Dec 25, 2023 at 21:44

You must log in to answer this question.

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