15

We have custom object CustomObject__c and platform event Platform_Event__e. Whenever any of CustomObject__c records are modified, platform event should be published. We need to write test class to cover the trigger but it is hard to think up an assert statement for such test class method.

trigger CustomObject on CustomObject__c (after update) {
    EventBus.publish(new Platform_Event__e());
}

@isTest private class CustomObjectTriggerTest {
    static testMethod void testTrigger() {
        CustomObject__c rec = new CustomObject__c(Name = 'A');
        Test.startTest();
            rec.Name = 'b';
            update rec;
        Test.stopTest();
        //System.assert( ... ??? Is there anything here we can assert? )
    }
}
4
  • The fact you are publishing this event from a trigger is completely irrelevant to your question. Your essential question seems to be how to cover any method which results in an event being published, which is more general.
    – Adrian Larson
    Jan 16, 2018 at 16:39
  • if your platform event is consumed by a trigger, and the trigger updates some sobject, you can verify that way - but otherwise, this problem is not directly solvable except through indirect means such as your answer. I believe (safe harbor) that SFDC is addressing this
    – cropredy
    Jan 16, 2018 at 20:24
  • @AdrianLarson yes, the question is about covering method which only publishes a platform event without side effects
    – Patlatus
    Jan 17, 2018 at 10:16
  • 1
    @cropredy In my case platform event is not consumed by another trigger but by Lightning component, so platform event publishing doesn't have any side effects, which means there are no other object update.
    – Patlatus
    Jan 17, 2018 at 11:01

4 Answers 4

20

My colleague suggested the following solution. Events can be stacked in static property and then checked in assert.

So trigger can be refactored like following

trigger CustomObject on CustomObject__c (after update) {
    CustomObjectService.publishEvent();
}

and method can be extracted into service class

public class CustomObjectService {
    @testVisible private static List<Platform_Event__e> eventList = new List<Platform_Event__e>();
    public static void publishEvent() {
        Platform_Event__e eve = new Platform_Event__e();
        eventList.add(eve);
        EventBus.publish(eve);
    }
}

and test for trigger can be rewritten in the following form

@isTest private class CustomObjectTriggerTest {
    static testMethod void testTrigger() {
        CustomObject__c rec = new CustomObject__c(Name = 'A');
        insert rec;
        Test.startTest();
            rec.Name = 'b';
            update rec;
        Test.stopTest();
        System.assert( 1, CustomObjectService.eventList.size(), 'There should be one element in the list' );
    }
}
6
  • 1
    This would be true if all the Platform Events were published immediately. But that is not the case. We can determine if the event will be published ONLY if the commit of the current transaction succeeds. So, just by using this DOES NOT ENSURES your event was fired for real. Jun 20, 2021 at 17:11
  • This solution, and the similar one below it, seems to have the drawback of having a perpetually growing eventList object while the code is running under regular usage.
    – Glen Mazza
    Jul 1, 2021 at 1:33
  • @GlenMazza No, it is not, the eventList property lives during transaction, and transaction lives up to 10 seconds in synchronous mode or 60 seconds in asynchronous mode
    – Patlatus
    Jul 1, 2021 at 7:21
  • @Patlatus Thanks for the response. I'm a Java developer, and just starting to learn Apex. In Java, CustomObjectService's static class variable eventList would have a lifespan of that of its class (forever, once it is in memory) and the data it holds would also be forever unless explicitly emptied out. I'm having trouble seeing how it could be different in Apex.
    – Glen Mazza
    Jul 1, 2021 at 8:21
  • 2
    In Apex, static class variable has lifespan of the transaction, which might be up to 10 or 60 seconds
    – Patlatus
    Jul 2, 2021 at 12:05
4

My suggestion would be to 'instrument' the EventBus.publish method by creating a wrapper like so:

public class EventBusInstr {
public static List<SObject> events = new List<SObject>();
public static void publish(SObject event) {
   events.add(event);
   EventBus.publish(event);
  }
}

Next, use this wrapper instead of the built-in EventBus.publish method in your code:

CustomEvent__e event = new CustomEvent__e(Name__c = 'Custom');    
EventBusInstr.publish(event);

And finally, assert the number of events your wrapper publishes like so:

System.assert(EventBusInstr.events.size() == 1);
2
  • 1
    More traditionally this would be called a decorator pattern.
    – Adrian Larson
    Aug 17, 2019 at 17:04
  • 1
    This would be true if all the Platform Events were published immediately. But that is not the case. We can determine if the event will be published ONLY if the commit of the current transaction succeeds. So, just by using this DOES NOT ENSURES your event was fired for real. Jun 20, 2021 at 17:11
2

To me, the simplest approach I could think of, that ensures the event is actually being published (and fired), was to use the event's Trigger to set some static variables that the Test Class can access. This is similar to a trigger I have written for a similar use case (but obviously different variables):

trigger MyEvent on MyEvent__e (after insert) {
    for (MyEvent__e event : Trigger.new) {
        MyEvent_Test.FIELD1_VALUE = event.MyField1__c;
        MyEvent_Test.FIELD2_VALUE = event.MyField2__c;
    }
}

The Test Class can hold the the static variables that are being updated by the simple Trigger, and assert that those variables hold the desired values.

@isTest
public class MyEvent_Test {
    public static String FIELD1_VALUE = null;
    public static String FIELD2_VALUE = null;

    @isTest
    private static void myEventTest() {
        Test.startTest();

        // Replace with your code that actually launches event, in your case
        // it could be simply insert new CustomObject__c() with the desired
        // values populated.
        EventBus.publish(new List<MyEvent__e>{
            new MyEvent__e(
                MyField1__c = 'value1',
                MyField2__c = 'value2'
            )
        });

        Test.stopTest();

        // Force delivery of event if it happens in async process
        // Test.getEventBus().deliver();

        // The MyEvent.trigger will have set the static variables
        System.assertEquals('value1', FIELD1_VALUE);
        System.assertEquals('value2', FIELD2_VALUE);
    }
}

This is a very simple use case, but one I have implemented with passing tests. If there are more events that need to be published, instead of simple String static variables, you could imagine static Lists instead.

To me, the decorator pattern in the other answers is certainly a good option, but it doesn't test that the event is actually firing, only that it's being put into the Event Bus. It's a subtle difference, but could be an important one if the timing of the published event is important (as it was in my case).

2

EventBus.publish() returns Database.SaveResult objects. Try storing them in a class variable and asserting on them.

For example, write a trigger handler like this:

public with sharing class CustomObjectTriggerHandler {
  @TestVisible
  private List<Database.SaveResult> publishResults;

  public void onAfterUpdate(List<CustomObject__c> customObjects) {
    publishResults = EventBus.publish(
      new List<Platform_Event__e>{ new Platform_Event__e() }
    );
  }
}

And test it like this:

@IsTest
private class CustomObjectTriggerHandlerTest {
  @IsTest
  private static void onAfterUpdate_publishesPlatformEvents() {
    CustomObjectTriggerHandler triggerHandler = new CustomObjectTriggerHandler();
    
    triggerHandler.onAfterUpdate(new List<CustomObject__c>());

    System.assertEquals(1, triggerHandler.publishResults.size());
    System.assertEquals(true, triggerHandler.publishResults[0].success);
  }
}
2
  • well, this is technically the same answer which was originally posted
    – Patlatus
    Dec 29, 2022 at 11:08
  • The other answer[1] suggests adding elements to a List<Platform_Event__e> and asserting on how many elements are in the list. It doesn't assert that the events were published successfully. This answer assigns List<Database.SaveResult> after calling EventBus.publish() and asserts that the events were published successfully. [1]: salesforce.stackexchange.com/a/204806/54914 Dec 29, 2022 at 23:48

You must log in to answer this question.

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