11

I have a batch class which implements the Database.RaisesPlatformEvents interface. This interface has recently been introduced and helps log unexpected exceptions: https://developer.salesforce.com/docs/atlas.en-us.218.0.object_reference.meta/object_reference/sforce_api_objects_batchapexerrorevent.htm

It works well and I have implemented a trigger which calls a handler class to log the error permanently in a separate object:

trigger LogBatchApexErrorEvent on BatchApexErrorEvent (after insert){
    if(Trigger.IsInsert && Trigger.IsAfter){
        LogBatchApexErrorEvent_Handler.persistLog(Trigger.New);
    }
}

My test batch class looks somewhat like this:

public with sharing class TestBatch implements Database.Batchable<sObject>, Database.RaisesPlatformEvents {
    public Database.QueryLocator start(Database.BatchableContext BC){
        String query = 'SELECT Id FROM Account';
        return Database.getQueryLocator(query);
    }

    public void execute(Database.BatchableContext BC, List<SObject> scope){
        Integer i = 1 / 0;
    }

    public void finish(Database.BatchableContext BC){ }
}

The problem is, it seems impossible to write a test class for this. I currently have this test method:

static testMethod void testBatchApexErrorEvent() {
    insert new Account(Name = 'Test Account');

    try {
        Test.startTest();
            TestBatch tb = new TestBatch();
            Database.executeBatch(tb); 
        Test.stopTest();
    } catch(System.MathException e){}

    System.assertEquals(1, [SELECT Id FROM Log__c].size());
}

However, the BatchApexErrorEvent is a Platform Event so is asynchronous - which means that at the time my System.assertEquals line runs it hasn't actually fired yet.

I also tried throwing a BatchApexErrorEvent manually, but I get the following error: DML operation Insert not allowed on BatchApexErrorEvent. This is my attempt:

BatchApexErrorEvent event = new BatchApexErrorEvent();
EventBus.publish(event);

Despite this being the way BatchApexErrorEvents are tested in this blogpost on the topic, it doesn't seem possible anymore: https://developer.salesforce.com/blogs/2019/01/building-a-batch-retry-framework-with-batchapexerrorevent.html

Considering there is no way to either a) test two asynchronous processes in one test method or b) manually throw a BatchApexErrorEvent, is there currently any way for me to test this? :-)

10

So as I can see from your code Divide by zero will throw an exception. Which inturn will fire BatchApexErrorEvent . The thing we need to look for is this is the test context. The events will not deliver unless you force it to deliver.

Thus you have to call Test.getEventBus().deliver();, this will ensure your triggers on platform events are fired and you can do successful assert.

static testMethod void testBatchApexErrorEvent() {
   insert new Account(Name = 'Test Account');

   try{
        Test.startTest();
        TestBatch tb = new TestBatch();
        Database.executeBatch(tb); 
        Test.getEventBus().deliver();
        Test.stopTest();
    } catch(System.MathException e){}
    Test.getEventBus().deliver(); 

    System.assertEquals(1, [SELECT Id FROM Log__c].size());
}

Source: Deliver Test Event Messages on Demand with Test.getEventBus().deliver()

| improve this answer | |
  • I'm afraid that doesn't work, if I remove the try-catch block the test fails with: 'System.MathException: Divide by 0'. If I reintroduce the try-catch block, however, my trigger doesn't seem to be firing - regardless of whether I put the 'Test.getEventBus().deliver();' before or after 'Test.stopTest();'. – Koen Wesselman May 23 '19 at 9:23
  • Ah, got it - I moved the 'Test.getEventBus().deliver();' after the try-catch block and that did it. I'll edit your answer and mark it as correct - the key was that method! – Koen Wesselman May 23 '19 at 9:27

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.