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 at 19:42
  • @DanielBallinger It's in the class that publishes the event
    – Mike
    Jan 12 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 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 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 at 18:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.