I have recently written some asynchronous apex that is utilizing the chaining feature that queueable apex offers. In summary, I am calling a queueable apex class (TestQueue1) that then calls another queueable apex class (TestQueue2). The code itself functions wonderfully, but now I am in the dreaded phase of writing my apex tests in order to get my code deployed, and I am having troubling figuring out how to test the chained queueable apex. I have simplified the process to make it easy to follow and rule out any side effects. Here is my test class:
@isTest
public class CalculateEmailRecipientsTest {
@testSetup
static void setupTestData() {
TestInsertHistoricalData.setupTest();
TestInsertHistoricalData.insertHistoricalData(1, 0);
}
@isTest
static void runCalculateEmailRecipients() {
List<Account> accounts = [
SELECT Id,
Name,
Realtime_Balance__c,
Days_Balance_Outstanding__c,
IsActiveMember__c
FROM
Account
];
System.debug('accounts: ' + accounts);
TestQueue1 invocableJob = new TestQueue1(accounts);
Test.startTest();
Id jobId = System.enqueueJob(invocableJob);
Test.stopTest();
}
}
Due to the debug log, I have guaranteed that I am getting accounts back (which is irrelevant for this simplified test). Here is the TestQueue1 queueable apex class that the test invokes:
public class TestQueue1 implements Queueable {
private List<Account> accounts;
public TestQueue1(List<Account> accounts) {
this.accounts = accounts;
}
public void execute(QueueableContext context) {
System.debug('in testQueue1: ');
if(!accounts.isEmpty()) {
TestQueue2 invocableJob = new TestQueue2();
Id jobId = System.enqueueJob(invocableJob);
}
}
}
As you can see, this is an extremely basic queueable apex class that does nothing except call a second queueable apex class called TestQueue2. Here is that class:
public class TestQueue2 implements Queueable {
public TestQueue2() {}
public void execute(QueueableContext context) {
System.debug('in testQueue2: ');
}
}
Again, this is a super simple queueable apex class and does nothing except print out a debug log. However, I am getting the following error when attempting to execute the test:
FATAL_ERROR System.AsyncException: Maximum stack depth has been reached.
This error is very puzzling because it typically indicates that a queueable process is somehow calling itself, even indirectly through the process it calls or through a trigger; however, as you can see, the queueable processes I have are certainly NOT calling themselves, and I have no triggers affecting this (as I am not performing any DML operations within the queueable classes). So I am at a loss. As a note, I noticed that if I commented out the queued job to the 2nd test class from the 1st test class, the error went away, indicating that salesforce tests have some sort of problem testing chained apex.
What is going on here? I sure would appreciate some ideas!! Thanks so much.
In unit test you have "room for one". When Test.stopTest()
finishes all queueables, batches etc - there must be at most 1 queueable executed. A batch job must finish with at most 1 execute
. The way your code is structured it'd try to execute two queueable jobs.
Common trick to solve it is to cheat. Instead of System.enqueueJob(invocableJob)
- call the important bit directly: invocableJob.execute(null);
. That's still "normal" context, not queueable so if it has its own enqueue inside - it'll still work.
Another way is nicely described in https://salesforce.stackexchange.com/questions/181233/system-asyncexception-maximum-stack-depth-has-been-reached