function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
dwright01dwright01 

How to test scheduled apex that executes a batch job

I wrote some apex and would like to write a test for it, but am stumped as to how to do it!

The code to be tested is an apex "after update" trigger that schedules some apex code to run in 2 seconds, and this code in turn starts a batch job.  The job runs execute() and finish(), and I want my test code to verify that the execute() and finish() did the right the right things.

The test code is like this:
<create records>
Test.startTest();
update acct;
Test.stopTest();
<asserts that the execute and finish code did the right things>

But, through use of logging, I see that the scheduled apex code ran fine, and started the batch job, but the execute() and finish() code happens after my asserts and the test routine finishes!   This is not at all what I expected would happen from reading the documentation.  It is good (for coverage) that the code runs, but m test is not able to assert that the code worked.

I cannot figure out how to write the test to verify that the actions the trigger did, including running the batch job, worked correctly. 

The only thing I can think of is to write a separate test simulating the scheduled apex code itself.

Anyone else have any ideas?

Thanks, Dave
Best Answer chosen by dwright01
dwright01dwright01
Just thought I'd let everyone know - -

I opened a case to Salesforce Support asking about this.  The support person tested it and discussed with the dev team.  It is working as designed, but he agreed that it would be better if all the asynch processed thar would be run as a result of the code after startTest() actually were completed by the time stopTest() returned.   He was going to request that this functionality be implemented in a future release.

All Answers

pconpcon
After the .stopTest is run, the batch methods should complete and you should be able to assert that things happened.  Can you provide the offending code and the test that is not asserting correctly?
dwright01dwright01

Ok you can see the problem by creating the following 3 classes:
---
@isTest
    private class testScheduledClassThatStartsBatchJob{
    static testMethod void myTest() {
        Account acct = new Account(Name = 'Some Account');
        insert acct;
       
        Test.startTest();
        System.debug('*** startTest called');
        SchedulableApexBatch.CallExecuteMomentarily();
        Test.stopTest();
        System.debug('*** stopTest called');
    }
}
----------------------------------
global class SchedulableApexBatch Implements Schedulable {
    // This is called when the scheduled task runs. It finds the first ApexBatch__c record to be processed and schedules a batch
    // job to run it if we find one and the job is not scheduled or running.
    global void Execute(SchedulableContext ctx) {
        Database.executeBatch(new BatchClass());
    }

    // return a Cron expression to schedule the job at a specific time
    private static String GetSchedulerExpression(DateTime dt) {
         return '' + dt.second() + ' ' + dt.minute() + ' ' + dt.hour() + ' ' +  dt.day() + ' ' + dt.month() + ' ? ' + dt.year();    
    }

    public static void CallExecuteMomentarily()  {
        System.schedule('ScheduledApexBatch',
            GetSchedulerExpression(DateTime.Now().addSeconds(2)),
            new SchedulableApexBatch());
    }
}
-------------------------------------
global class BatchClass implements Database.Batchable<sObject> {
    global Database.QueryLocator start(Database.BatchableContext BC) {
        return Database.getQueryLocator('SELECT id FROM Account');
    }
   
    global void execute(Database.BatchableContext BC, List<sObject> scope) {
        // delegate the execution of this context
        System.debug('*** execute');
    }
   
    // finish the job. Stores status of this job, then if there is another general batch job queued up, schedule it
    global void finish(Database.BatchableContext BC) {
        System.debug('*** finish');
    }  
}
--------------------------------------

Turn on debugging, and run the test.  If you look at the logs you will see that the test's stopTest has returned, THEN the batch job routines are called.
This is too late for the test to assert that these batch operations did what they were supposed to do.

 

Pramod_SFDCPramod_SFDC
Hi,

I think the below link might help you provide you more insight.

>> http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_scheduler.htm



Regards
Pramod
pconpcon
That is the expected behavior.  You will then just add your System.asserts after the Test.stopTest.  The stopTest does not mean that you cannot use any more asserts, it is just used to isolate your test from your setup DML and your after DML to assure that any limit errors you hit are because of the code not because of you setup.

So in your example if you did some modification of accounts in your scheduled class then after your Test.stopTest you would do a SOQL query to fetch the account data back down and then do the System.assert to determine that the data changed as expected.
dwright01dwright01
pcon,  No - the asserts fail in my test.  The batch job runs AFTER the Test.stopTest() returns and the test does its asserts.  I can only assert things that happen in the scheduled apex, not in the batch processing.
dwright01dwright01
Pramod, the documentation says:  "All asynchronous calls made after the startTest method are collected by the system. When stopTest is executed, all asynchronous processes are run synchronously".  The batch job processing is asynchronous but it is not executed before the stopTest returns.  If it were, I would be able to have my test assert that the batch operations had been completed when stopTest returned.
dwright01dwright01
Just thought I'd let everyone know - -

I opened a case to Salesforce Support asking about this.  The support person tested it and discussed with the dev team.  It is working as designed, but he agreed that it would be better if all the asynch processed thar would be run as a result of the code after startTest() actually were completed by the time stopTest() returned.   He was going to request that this functionality be implemented in a future release.
This was selected as the best answer