[FACT] Future Method called from a Schedulable Cannot be Tested.

 

 



As we Know, We can have only 1 Test.startTest() and Test.stopTest() per method, 

Each test method is allowed to call this method only once, reference Official Documentation 

If you have a schedulabe class calling a future method, or any other asychronous process chained in this way. They cant be tested.

Due to the following reasons:

  1. Salesforce's handling of asynchronous operations can make testing a future method from a schedulable class challenging. 
  2. Asynchronous execution can cause issues as the future method may not complete before the test asserts results. 
  3. Salesforce's Test.startTest() and Test.stopTest() control asynchronous code execution, but calling a future method from within a schedulable class complicates the execution flow. 
  4. Test methods not waiting for asynchronous calls can also cause issues. 
  5. Salesforce enforces strict limits on asynchronous operations within a test context, and the platform's isolation of test data may prevent future methods from accessing or modifying expected data, leading to test assertion failures. 

Example

Schedulabe class:


public class ScheduledFuture implements Schedulable {
    public void execute(SchedulableContext context) {
        // For example, we schedule the future method to process a list of Account records
        List<Account> accounts = [SELECT Id FROM Account LIMIT 10]; // Fetch some sample records
        List<Id> accountIds = new List<Id>();
        
        for (Account acc : accounts) {
            accountIds.add(acc.Id);
        }
        
        // Call the future method
        FutureMethods.processRecords(accountIds);
    }
}

Future:


public class FutureMethods {
    @future
    public static void processRecords(List<Id> recordIds) {
        // Simulate a long-running operation
        for (Id recordId : recordIds) {
            // For example, update some records or perform an action
            Account acc = [SELECT Id, Name FROM Account WHERE Id = :recordId LIMIT 1];
            acc.Name = 'Updated Name ' + System.currentTimeMillis();
            update acc;
        }
    }
}

Test code:


@isTest
private class ScheduledFutureTest {
    @isTest
    static void testScheduledFuture() {
        // Insert some sample accounts to test with
        List<Account> accounts = new List<Account>();
        for (Integer i = 0; i < 5; i++) {
            accounts.add(new Account(Name = 'Test Account ' + i));
        }
        insert accounts;
        
        // Create the cron expression for scheduling
        String cronExp = '0 0 0 * * ?'; // Every day at midnight
        
        // Schedule the job
        Test.startTest();
        System.schedule('Test ScheduledFutureJob', cronExp, new ScheduledFuture());
        Test.stopTest();
        
        // Verify that the future method was called and processed the records
        // Check the AsyncApexJob object for the status of the future method
        List<AsyncApexJob> jobs = [SELECT Status, JobType FROM AsyncApexJob WHERE JobType = 'Future'];
        System.assertEquals(1, jobs.size(), 'Future method should have been executed');
        System.assertEquals('Completed', jobs[0].Status, 'The future method should have been completed');
        
        // Optionally, check if records were updated
        List<Account> updatedAccounts = [SELECT Name FROM Account WHERE Id IN :accounts];
        for (Account acc : updatedAccounts) {
            System.assert(acc.Name.startsWith('Updated Name'), 'Account name should have been updated');
        }
    }
}
References:

https://salesforce.stackexchange.com/questions/61239/testing-scheduled-apex-containing-future-method
https://salesforce.stackexchange.com/questions/244791/how-do-i-test-asynchronous-apex
https://salesforce.stackexchange.com/questions/311695/unit-testing-schedulable-that-calls-future-method 

Comments