Problems with testing scheduled and async trigger flows

lut 20, 2023

Trigger flows are very powerful. Using point-and-click solutions, consultants may customize salesforce using point-and-click instead of coding. On the other side, this is a pretty new solution and some of its aspects seem not to be well designed.

One of the biggest issues related to async trigger flows is the lack of the possibility of testing them by apex unit tests. Simply, the async part of the flow is not executed during the unit test. This is counter-intuitive because both the future method and queueable are executed before the Test.stopTest() method.

Please consider the following example:

1 I have created a custom object B__c, with three custom fields (checkboxes),

2 For the object, I have created a trigger with trigger handler that executes the queueable job and future method. As you can see below this code is pretty straightforward. Trigger calls a run() method from TriggerHandler. TriggerHander at first checks if this was executed from async context and if not it calls future methods that sets IsFutureProcessed__c flag and then queues queueable job that sets IsQueueableProcessed__c flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
trigger BTrigger on B__c (after insert, after update) {
	 if(Trigger.IsAfter){
         if(Trigger.IsInsert || Trigger.IsUpdate) {
          	BTriggerHandler.run(Trigger.newMap);
         }
     }
}
 
public class BTriggerHandler {
 
    public static void run(Map<Id,B__c> Id2BMap) {
        if(System.isQueueable() || System.isFuture()) {
            return; // stop execution if you are already in Queueable context to avoid infinite loop or mixing queable and future calls;
        } 
        Set<Id> BIds = Id2BMap.keySet();
        futureMethod(BIds);
        QueueableClass theQueableJob = new QueueableClass(BIds);
        System.enqueueJob(theQueableJob);
    }
 
    @future
    public static void futureMethod(Set<Id> BIds) {
    	 List<B__c> Bs = [SELECT IsFutureProcessed__c FROM B__c WHERE Id IN :BIds];
            for(B__c theB : Bs){
                theB.IsFutureProcessed__c = true;
            }
            update Bs;    
    }
 
    public class QueueableClass implements Queueable {
 
        private Set<Id> BIds;
 
        public QueueableClass(Set<Id> BIds) {
        	this.BIds = BIds;    
        }        
 
        public void execute(QueueableContext context) {
            List<B__c> Bs = [SELECT IsQueueableProcessed__c FROM B__c WHERE Id IN :BIds];
            for(B__c theB : Bs){
                theB.IsQueueableProcessed__c = true;
            }
            update Bs;
        }      
    } 
 
}


3 Also I have created the simple trigger flow:

 

This flow does not do too much. Only runs async path and set IsAsyncFlowUpdate__c flag on the B__c object to true.

To check if this flow works correctly I have inserted record B__c:

1
2
3
4
List<B__c> bs = [Select id from B__c];
delete bs;
B__c theB = new B__c();
insert theB;

(at the beginning I have deleted all instances of B__c SObject from the organization)

Then when I queried the B__c records I got:

As we expected all flags (IsAsyncFlowProcessed__c, IsFutureProcessed__c, IsQueueableProcessed__c) were set to true.

Unfortunately when we move try to write a unit test things are getting much more complicated.

I have prepared a simple test class with one method to unit test this scenario:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@isTest
public class BTriggerHandler_Test {
 
    testmethod public static void testAsyncProcessing(){
        B__c theB = new B__c();
 
        Test.startTest();
 
        	insert theB;
 
        Test.stopTest();
 
        B__c theBfromDB = [
            SELECT Id, IsQueueableProcessed__c, IsAsyncFlowProcessed__c, IsFutureProcessed__c
            FROM B__c
            LIMIT 1
        ];
 
        System.assertEquals(true, theBfromDB.IsQueueableProcessed__c, 'Queable job did not finish.');
        System.assertEquals(true, theBfromDB.IsFutureProcessed__c, 'Future method job did not finish.');
        System.assertEquals(true, theBfromDB.IsAsyncFlowProcessed__c, 'Async flow did not finish.');        
    }    
 
}

As you can see this test does the same activity that I have done previously. Inserts the B__c record, search it in the database and checks if all flags(IsAsyncFlowProcessed__c, IsFutureProcessed__c, IsQueueableProcessed__c) were set.

Surprisingly when I have executed the test I got an error:

It simply means that Test.stopTest() ends all async processing except async flows.

My colleagues were so shocked that they raised the case to Salesforce and the response from Salesforce support was:
„For now you cannot test both async and scheduled flows if you need this functionality please create an idea and vote for it.”

As we learned Salesforce flows are easy and powerful, but for the scheduled and async flows you cannot write unit tests and there is no workaround for it. 

Author: Bartosz Borowiec