6

EDIT for clarification of title:

CronTrigger is probably actually firing at some point in the testing process - what is wierd is that CronTrigger.TimesTriggered is NOT incrementing after Test.stopTest()

Any asynchronous calls made within the Test.startTest() and Test.stopTest() methods of a test class should execute upon hitting Test.stopTest().

So, presumably, if I schedule a class using System.schedule(), the CronTrigger object representing that scheduled job should fire immediately upon Test.stopTest(), regardless of CronTrigger.NextFireTime's value.

Here is my schedulable class:

global class Schedulable_Nightly implements Schedulable {

    //PREDEFINED_SCHEDULE = "Every day at midnight"
    public static String PREDEFINED_SCHEDULE = '0 0 0 * * ?';

   global void execute(SchedulableContext SC) {
      Database.executeBatch(new ContractTimelineBatch());
   }
}

And here is my test method, pretty much lifted from SF documentation:

private class TestContractTimelineBatch {           

    @isTest static void test_schedulable(){

       Test.startTest();

          // Schedule the test job
          String jobId = System.schedule('testBasicScheduledApex',
          Schedulable_Nightly.PREDEFINED_SCHEDULE, 
             new Schedulable_Nightly());

          // Get the information from the CronTrigger API object
          CronTrigger ct = [SELECT Id, CronExpression, CronJobDetailId, CronJobDetail.Name, TimesTriggered, PreviousFireTime, NextFireTime, State
             FROM CronTrigger WHERE id = :jobId];

          // Verify the job has not run
          System.assertEquals(0, ct.TimesTriggered);

       Test.stopTest();

       ct = [SELECT Id, CronJobDetailID, TimesTriggered, PreviousFireTime, NextFireTime, State
         FROM CronTrigger WHERE id = :jobid];

       //DEBUG STATEMENT SHOWS THAT ct.TimesTriggered IS STILL 0
       system.debug('Cron - post: ' + ct);

       // Verify that the job has now run   
       System.assertEquals(1, ct.TimesTriggered);

    }

}

This final assertEquals fails because ct.TimesTriggered stays at 0, both before and after Test.stopTest

What gives?

  • 1
    don't you need to requery CronTrigger after stopTest()? – cropredy Mar 23 '16 at 19:58
  • Yes and I had, but mistakenly excluded it from my post for some reason. Issue isn't resolved by the requery however. – smohyee Mar 23 '16 at 20:35
  • 1
    This looks like a bug. I'm going to see if I can verify this. – sfdcfox Mar 24 '16 at 2:57
  • gist.github.com/brianmfear/7564980aac77942fb535 proves that the code is running, but the TimesTriggered value doesn't increment. I don't know if this is documented behavior, but I'll see what I can find. – sfdcfox Mar 24 '16 at 3:12
  • @sfdcfox Thanks for your interest. I agree that the scheduled code is executing after Test.stopTest()... I suppose this suggests that test methods handle asynchronous execution in a nonstandard way (eg reads the created CronTrigger and executes separately without updating the CronTrigger record, or something). – smohyee Mar 24 '16 at 15:04
3

You could take a different approach to your test method

Instead of asserting whether the schedulable class was, in fact, executed, you should be asserting whether the schedulable's execute() method actually did anything and what you expected. Testing that SFDC can schedule/execute a schedulable per its CronTrigger proves nothing - as SFDC is guaranteed to execute your code at or around the time specified in the CronTrigger.

In your case, whether the batch job processed the mocked sobjects is the interesting thing to test.

  • cropredy, I understand your point. The SF documentation's sample test class for a basic scheduled class works the way you describe: they don't count 'timestriggered', they check changes resulting from the actual scheduled logic. – smohyee Mar 23 '16 at 20:42
  • BUT, that doesn't really address the issue I'm raising here, which is that timetriggered is NOT incrementing, suggesting that the scheduled job is NOT firing at Test.stopTest(). Understanding what's going on is the goal of my post (as opposed to actually testing functionality) – smohyee Mar 23 '16 at 20:43
  • others may chime in here but there are sufficient "adaptations/accommodations" in the way SFDC testmethods work with async stuff versus production that I just "moved on" and stopped worrying about it :-) – cropredy Mar 23 '16 at 20:47
2

As of 16-August-2018 this appears to be an issue. Note the following apex test method.

@isTest static void myTestMethod() {

    System.Test.StartTest();
        String jobId = System.schedule(xxx, xxx, xxx);
    System.Test.StopTest();

    //Assertion failed. Expected "Finished" actual "Waiting"
    System.assertEquals('Finished', [select State from CronTrigger where Id = :jobId ][0].State);
}
1

You need to query for the CronTrigger record for the scheduled job again after test.stopTest() to get the up-to-date value for TimesTriggered. Same as any other object you use a SOQL query to get.

  • 1
    Sorry, I had removed that line from the example I pasted for some reason. I did indeed requery crontrigger after test.stopTest(), and timestriggered was still 0. Updating code in post to reflect that. – smohyee Mar 23 '16 at 20:34
  • (I threw a system.debug('Cron - post: ' + ct); line after requerying and confirmed that ct.TimesTriggered didn't increment) – smohyee Mar 23 '16 at 20:37
  • So much for the easy answer. I'm afraid that I'm stumped. – Derek F Mar 23 '16 at 20:42
  • One other difference from sample code provided by SF documentation is that I have SeeAllData=True set, which is necessary for another test method in that same test class. Can't imagine it's relevant, but then IDK... – smohyee Mar 23 '16 at 20:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.