How to test a scheduler which runs a batch?

I have a scheduler class which calls a batch Apex. I want to write an Apex test for the scheduler class. However, the test fails. It seems that the testing framework does not execute the batch class.

I’m providing below a complete example that illustrates the problem. I wrote two test methods. The first one calls directly the batch, and this test method is successful. The second test method uses the scheduler instead of the batch. Since the scheduler class calls the batch, I’m expecting the same result than the first test method. However, the assertion fails in the second test method.

@isTest
public class AccountBachAndSchedulerTestSuite {
    static Integer numberOfAccounts {get; set;}
    static {
        numberOfAccounts = 50;
    }

    // Working test: we insert accounts, then we call the batch which sets
    // the Is_Checked__c field to true for all accounts
    @isTest static void testBatchOnly () {
        List<Account> lst_accounts = new List<Account>();
        for(Integer i = 0; i < numberOfAccounts; i++) {
            lst_accounts.add(New Account(
                Name = 'Test Account' + i,
                Is_Checked__c = false
            ));
        }
        insert lst_accounts;

        // call the batch
        Test.startTest();
        ID BatchProcessdId = Database.executeBatch(new AccountBatch());
        Test.stopTest();

        // Assert result: Is_Checked__c field must be set to true for all accounts
        Integer expectedNumOfAccounts = [SELECT COUNT() FROM ACCOUNT WHERE Is_Checked__c = true];
        System.assertEquals(numberOfAccounts, expectedNumOfAccounts);   // Assertion OK
    }

    // Failing test: same test than the first one. Only this time we call the
    // scheduler class instead of the batch
    @isTest static void testScheduler () {
        // CRON expression: midnight on March 15. Because this is a test, 
        // job is supposed to execute immediately after Test.stopTest()
        String cronExpr = '0 0 0 15 3 ? 2022';
        String jobRunningTime = '2022-03-15 00:00:00';

        List<Account> lst_accounts = new List<Account>();
        for(Integer i = 0; i < numberOfAccounts; i++) {
            lst_accounts.add(New Account(
                Name = 'Test Account' + i,
                Is_Checked__c = false
            ));
        }
        insert lst_accounts;

        Test.startTest();
        // Schedule the test job
        String jobId = 
            System.schedule('myJobTestJobName', cronExpr, new AccountBatchScheduler());
        Test.stopTest();

        // Assert result: Is_Checked__c field must be set to true for all accounts
        Integer actualNumOfAccounts = [SELECT COUNT() FROM ACCOUNT WHERE Is_Checked__c = true];
        System.assertEquals(numberOfAccounts, actualNumOfAccounts); // Asertion FAILED: 
        // Expected numberOfAccounts (=50), but actual is zero.
    }
}

Here is the Scheduler class:

global class AccountBatchScheduler implements Schedulable {
    global void execute(SchedulableContext sc) {
        ID lv_BatchProcessdId = Database.executeBatch(new AccountBatch());
    }
}

Here is the batch class:

// This batch sets the field Is_Checked__c to true for all accounts
global class AccountBatch implements Database.Batchable<sObject>, Database.Stateful {
    global final String myQuery = 'SELECT Id, Is_Checked__c FROM Account';
    global String errMsg = '';

    global Database.QueryLocator start(Database.BatchableContext BC) {
        return Database.getQueryLocator(myQuery);
    }

    global void execute(Database.BatchableContext BC, List<sObject> scope) {
        System.debug('Batch [AccountBatch] execute');
        List<Account> lst_accounts = (List<Account>) scope;
        for(Account acc : lst_accounts) {
            acc.Is_Checked__c = true;
        }
        // Update all Accounts
        if (!lst_accounts.isEmpty()) {
            Database.saveResult[] lst_updatetResults = Database.update(lst_accounts, false);
            for (Database.SaveResult singleResult : lst_updatetResults) {
                if (!singleResult.isSuccess()) {
                    errMsg += 'Batch [AccountBatch] Account Update error. ErrMsg = ' + 
                        singleResult.getErrors()[0].getMessage() + '<br />';
                }
            }
        }   
    }

    global void finish(Database.BatchableContext BC) {
        if (errMsg == '') {
            System.debug('Batch [AccountBatch] has finished : All Account updates are successful!');
        }
        else {
            System.debug('Batch [AccountBatch] has finished : There were errors. Here are a list of all errors. <br />' + errMsg);
        }
    }
}

Thank you for your help!!

Answer

I had a similar problem before which I solved by explicitly calling the execute method of my Schedulable class.

Try something like this:

Test.startTest();
AccountBatchScheduler abs= new AccountBatchScheduler();
String jobId = System.schedule('myJobTestJobName', cronExpr, abs);
abs.execute(null);
Test.stopTest();

Attribution
Source : Link , Question Author : Riadh Mankai , Answer Author : smukov

Leave a Comment