I am having trouble covering all lines of code in my test class for scheduled apex class. I'm relatively new to Apex and don't have an development background. Can anyone advise on how to get full test coverage for the below?

I'm querying all scheduled apex jobs and creating a custom object record for each scheduled job.


global class AllScheduledJobs implements Schedulable{

    global void execute(SchedulableContext sc) {

    //Check for scheduled jobs
    public void checkScheduleJobs()    {

        List<myObject> sl = new List<myObject>();        
        for(CronTrigger ct : [SELECT id, PreviousFireTime, NextFireTime, CronJobDetail.name  
                              FROM CronTrigger WHERE CronJobDetail.JobType = '7' AND PreviousFireTime = Today]){

                                  myObject logs = new myObject(Job_Name__c=ct.CronJobDetail.name, Type__c = 'Apex');   
        insert sl;        

1 Answer 1


There are some interesting challenges in designing a test case for this code, since it sort of eats its own tail in a way. Apex Scheduled Jobs are not hidden in test context, so if your org has existing Scheduled Jobs, any queries you make in test context will include them. Our tests should be resilient to that factor but not rely on it, since you don't want your tests to fail just because somebody unscheduled a job.

As an extra fun wrinkle, when a scheduled job runs as a result of Test.stopTest(), it does not update the PreviousFireDate on the associated CronTrigger, which your code relies upon.

Fundamentally, I think the test should schedule this job between Test.startTest() and Test.stopTest(), to ensure that it runs synchronously. Then, I'd write assertions for the myObject instance that we should expect this job itself to insert for itself. I wouldn't write any assertions against the total count of AsyncApexJob records or myObject records, though, because those have the potential to fail based on changes to the other scheduled jobs in your org.

I think the easiest way to get around the issue with PreviousFireDate, so that we can actually get that myObject record inserted, is going to be exposing the query as a string so that our test class can alter it to remove the PreviousFireDate filter.

We can sort of exploit this to create a built-in negative case, too. What you'd do is write two assertions: first, you force the scheduled job to run with Test.stopTest(), then, you assert that it did not create a myObject record for its own CronTrigger (since it doesn't yet have a PreviousFireDate). Then you create a new AllScheduledJobs instance, passing in a version of the query that doesn't have the PreviousFireDate filter. You then synchronously call checkScheduleJobs() again on that instance, and assert that it does in fact create a myObject record for the CronTrigger we created.

Here's one skeleton approach, that you'd have to modify based on your real data model:


System.assertEquals(0, [SELECT count() FROM myObject WHERE Job_Name__c = 'MY_TEST_JOB_NAME']);

Id jobId = System.schedule('MY_TEST_JOB_NAME', '0 5 * * * ?', new AllScheduledJobs());


System.assertNotEquals(null, jobId, 'Valid scheduled job id');
System.assertEquals(0, [SELECT count() FROM myObject WHERE Job_Name__c = 'MY_TEST_JOB_NAME']);

AllScheduledJobs a = new AllScheduledJobs();
a.query = 'SELECT id, PreviousFireTime, NextFireTime, CronJobDetail.name FROM CronTrigger WHERE CronJobDetail.JobType = \'7\'';

System.assertEquals(1, [SELECT count() FROM myObject WHERE Job_Name__c = 'MY_TEST_JOB_NAME']);

You'd just have to add a private, @TestVisible instance variable query to your scheduled class and default it to your actual query, then use Database.query() in checkScheduleJobs() to issue it.

I tried out this approach in my dev org, and I was able to get it to work and show 100% coverage.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .