5

As of Spring 23, we have several failing tests which involved publishing Platform Events, collecting them in a static variable, publishing them, then in the test, retrieving the static variable to assert size and contents.

It appears that this change (Apex Static Variables Are Reset Within a Transaction Between Groups of Platform Event–Triggered Flow Interviews) has a side effect that's clearing the static collections we were using in the tests, thus breaking them.

Without the ability to reference the published events in the static variable, I cannot come up with a good way to test that the events were published or what's in them. It seems my options are:

  1. Remove the assertions and only worry about coverage
  2. Create a listener for each event being published so as to be able to retrieve them after Test.stopTest().

Neither of these options are all that appealing. Am I overlooking another means to test?

3
  • Where is your static variable that is accumulating the events to be published located? Jan 11, 2023 at 19:42
  • @DanielBallinger It's in the class that publishes the event
    – Mike
    Jan 12, 2023 at 16:44
  • You should engineer this to allow mocking of the publication itself (e.g. encapsulate just the EventBus.publish in a mockable way - either a separate class you mock or as a virtual method you override in your test) for checking it gets published, and a separate unit test of the processing you initiate from the platform event subscriber - use best practice and put all of this in a trigger handler, not in the trigger.
    – Phil W
    Jan 30, 2023 at 21:38

1 Answer 1

0

One way to unit test whether a Platform Event is published with expected contents (without using listeners) is to use the Unit of Work pattern. An implementation of this is contained in the GitHub repo apex-common aka fflib.

Your code looks like the following

fflib_ISobjectUnitOfWork uow = Application.UnitofWork.newInstance();

...  construct the PlatformEvent here

uow.registerPublishAfterSuccessTransaction(myPlatformEvent);

...

uow.commitWork(); // this calls EventBus.publish()

and your testmethod mocks a UnitOfWork (in my example, using ApexMocks although you could also use Amoss) and then verifies that the registerPublishAfterSuccessTransaction was called with the expected argument

fflib_apexMocks mocks = new fflib_ApexMocks();

// Given mock UnitOfWork
fflib_SObjectUnitOfWork mockUow = (fflib_SObjectUnitOfWork) mocks.mock(fflib_SObjectUnitOfWork.class);
// Given mock UoW injected
Application.UnitOfWork.setMock(mockUow); 

// when code under test invoked
new MyClass().doStuff();

// then verify event constructed, constructed as expected, and published
((fflib_SObjectUnitOfWork)mocks.verify(mockUow,mocks.times(1)
                        .description('myEvent sb published')))
  .registerPublishAfterSuccessTransaction(
      fflib_Match.sObjectOfType(MyEvent__e__e.SObjectType)
  );

((fflib_SObjectUnitOfWork)mocks.verify(mockUow,mocks.times(1)
                        .description('myEvent sb published')))
  .registerPublishAfterSuccessTransaction(
      fflib_Match.sObjectWith(new Map<SObjectField,Object> {
         MyEvent__e.Field1__c => myExpectedField1Val,
         MyEvent__e.Field2__c => myExpectedField2Val
          })
  );

((fflib_SObjectUnitOfWork)mocks.verify(mockUow,mocks.times(1)
                        .description('commitwork sb called')))
  .commitWork();

Notes:

  1. Since the above is based on the fflib architecture, you need an Application.cls. This will include an entry for MyEvent__e in the UnitOfWork factory as follows:

    public static final fflib_Application.UnitOfWorkFactory UnitOfWork =
     new fflib_Application.UnitOfWorkFactory(
             new List<SObjectType> { // list in dependency order (e.g. Accounts before Contacts)
                ...
                MyEvent__e.SObjectType
             }
     );
    
  2. The approach above of course works for normal DML (registerNew(myObject), registerDirty(myObject), etc and even sending email (uow.registerEmail(email)) so you can see if your DML and/or outbound apex email was constructed properly in a unit test.

2
  • Thanks, so if I didn't have time to refactor our code to make use of fflib right now, I could mock the publisher class and do essentially the same thing, correct?
    – Mike
    Jan 10, 2023 at 17:56
  • yes, and the degree to which you want to mock can vary -- use a dependency-injected object to going all the way to the Test.StubAPI (which is what apexmocks and amoss are built on). The mocking frameworks are super powerful
    – cropredy
    Jan 10, 2023 at 18:20

You must log in to answer this question.

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