Queueable jobs in Salesforce are a powerful way to handle asynchronous processing, allowing you to chain jobs for scalability and efficiency. This blog post explores how to implement a chaining Queueable job in Apex, test it effectively, and use the AsyncOptions
class to control job behavior.
The AccountProcessingQueueable
class processes a set of Account
records in batches and chains additional jobs if more records remain to be processed. Below is the implementation:
public class AccountProcessingQueueable implements Queueable {
private Set<Id> accountIds;
private Integer batchSize;
public AccountProcessingQueueable(Set<Id> accountIds, Integer batchSize) {
this.accountIds = accountIds;
this.batchSize = batchSize;
}
public void execute(QueueableContext context) {
// Query accounts to process, limited by batchSize
List<Account> accountsToProcess = [
SELECT Id, Name, AnnualRevenue
FROM Account
WHERE Id IN :accountIds
LIMIT :batchSize
];
System.debug('Processing ' + accountsToProcess.size() + ' accounts');
// Perform complex calculations and updates
for (Account account : accountsToProcess) {
account.Description = 'Updated by AccountProcessingQueueable';
}
// Update accounts if there are any to process
if (!accountsToProcess.isEmpty()) {
update accountsToProcess;
}
// Remove processed account IDs from the set
for (Account account : accountsToProcess) {
accountIds.remove(account.Id);
}
// If there are more accounts to process, enqueue the next job
if (!accountIds.isEmpty()) {
System.enqueueJob(new AccountProcessingQueueable(accountIds, batchSize));
}
}
}
Creating a Test Class
Testing Queueable jobs requires creating test data, enqueuing the job, and verifying the results. The Test.startTest()
and Test.stopTest()
methods are used to ensure asynchronous jobs execute synchronously within the test context, allowing you to validate their behavior.
Below is the test class for the AccountProcessingQueueable
class:
@IsTest
public with sharing class AccountProcessingQueueableTest {
@IsTest
public static void testQueueable() {
// Create test data: 7 accounts
List<Account> accounts = new List<Account>();
for (Integer i = 1; i <= 7; i++) {
accounts.add(new Account(Name = 'Test ' + i));
}
insert accounts;
// Prepare account IDs
Set<Id> accountIds = new Map<Id, SObject>(accounts).keySet();
// Set up AsyncOptions to limit chaining depth
AsyncOptions asyncOptions = new AsyncOptions();
asyncOptions.maximumQueueableStackDepth = 4;
// Start test context and enqueue the job
Test.startTest();
System.enqueueJob(new AccountProcessingQueueable(accountIds, 2), asyncOptions);
Test.stopTest();
// Verify results
List<Account> updatedAccounts = [SELECT Id, Description FROM Account WHERE Id IN :accountIds];
for (Account account : updatedAccounts) {
System.assertEquals('Updated by AccountProcessingQueueable', account.Description,
'Account description should be updated by the Queueable job');
}
}
}
The Rationale Behind the Test Data
In the AccountProcessingQueueableTest
class, we create 7 account records to demonstrate the chaining mechanism of the Queueable job. With a batch size of 2, the job processes accounts in batches, requiring multiple chained executions to handle all 7 accounts. Specifically:
- The first job processes accounts 1–2 (2 accounts).
- The second job processes accounts 3–4 (2 accounts).
- The third job processes accounts 5–6 (2 accounts).
- The fourth job processes account 7 (1 account).
This setup ensures that the chaining logic is thoroughly tested, including the handling of partial batches in the final execution.
Understanding maximumQueueableStackDepth
The AsyncOptions
class, introduced in Salesforce, allows you to control the behavior of Queueable jobs, including the maximumQueueableStackDepth
property. This property limits the number of chained Queueable jobs that can be enqueued in a single execution context.
Key Points About maximumQueueableStackDepth
:
- The default value is 50, meaning up to 50 chained jobs can be enqueued in a single transaction.
- Setting
maximumQueueableStackDepth
to a lower value (e.g., 4) restricts the number of chained jobs to 3 additional jobs beyond the initial job (total of 4 jobs in the chain). - If the limit is exceeded, Salesforce throws a
System.AsyncException
with a message indicating that the maximum stack depth has been reached.
In the test class, we set maximumQueueableStackDepth
to 4 to demonstrate how to control chaining depth:
AsyncOptions asyncOptions = new AsyncOptions();
asyncOptions.maximumQueueableStackDepth = 4;
Running the Queueable Job
With 7 accounts and a batch size of 2, the Queueable job executes 4 times to process all records:
- Job 1: Processes 2 accounts (remaining: 5).
- Job 2: Processes 2 accounts (remaining: 3).
- Job 3: Processes 2 accounts (remaining: 1).
- Job 4: Processes 1 account (remaining: 0).
The Test.stopTest()
method ensures that all chained jobs complete before the test context ends, allowing you to verify the results immediately.