I have a Platform Event with a trigger I am writing tests for. This trigger inserts a record and throws a custom exception if there is a DML error. I want to make assertions about the message in that exception.

If I use EventBus.publish() and Test.stopTest(), the exception won't be thrown inside the test, since the trigger executes in a separate transaction. So instead, I have my trigger logic in an Apex class, which I can test directly.

So far so good, but here's the wrinkle: my trigger logic sets the OwnerId of the record I'm creating based on the CreatedById in the Platform Event. And Apex doesn't allow you to set CreatedById on the event record directly -- you have to actually create the record. Which is fine if you're using a regular custom object -- you can query that type of info. But Platform Events aren't queryable.

So now I'm getting a DML error, INVALID_CROSS_REFERENCE_KEY: Owner ID: owner cannot be blank -- but that isn't the DML error I'm trying to test for.

Is there anything I can do to set the CreatedById on the event? Or alternatively, is there any way to catch exceptions that come from a Platform Event trigger execution?

  • I don't have an easy way to test at the moment, but System.runAs(someUser) should work for this purpose, I would think. – sfdcfox Dec 3 '20 at 2:20

You can fabricate a platform event using something like:

String theUserId = ...;

Map<String, Object> eventMap = new Map<String, Object> {
        'attributes' => new Map<String, Object> {
                'type' => 'MyEvent__e'
        'MyField__c' => 'Xyz',
        'CreatedById' => theUserId

MyEvent__e fabricated = (MyEvent__e) JSON.deserialize(JSON.serialize(eventMap), MyEvent__e.class);

This deserialization approach allows you to fake out not just your custom fields but also the system fields as well, as shown above with the CreatedById. You can use this for platform events and ordinary SObjects too.

You can then pass this event into your apex trigger handler code as required.

  • It had occurred to me that I could always add a new custom field to the Platform Event instead of using CreatedById, but I like this solution better. FYI, I excluded the attributes part from my Map and it still worked fine. – Matthew Souther Dec 3 '20 at 17:13
  • Nice. Yes, you only need that when the data is being deserialized without an explicitly set object type in the JSON.deserialize. I included it because it is a common part of the SObject in JSON format. – Phil W Dec 3 '20 at 17:23

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.