[SalesForce] How to write test class for below scheduling code

I have set a schedule class which checks for running or queued batches. If there are more than 5 batches running then it gets execute after 30 min delay. But I am not able to cover the else part in test coverage. If any body can help me in this regard it would be a great help. Below is my code.

New modified class as per crop1645 suggested below :
global class LeadReminderSchedule implements Schedulable{

list<Lead> schedule = new list<Lead>(); 

IAsyncJob iGetJobs = new GetProdJobs();  // default to prod interface
//AsyncJob iTestGetJobs = new GetTestJobs();
// Interfaces and their implementation - prod vs test
public interface IAsyncJob {
    list<AsyncApexJob> getJobs();
}

public class GetProdJobs implements IAsyncJob {
    public list<AsyncApexJob> getJobs() {
        return [select id FROM AsyncApexJob WHERE JobType='BatchApex' AND (Status = 'Processing' OR Status = 'Preparing' OR Status = 'Queued')];  
    }
}

public class abc implements IAsyncJob {
    public list<AsyncApexJob> getJobs() {
        list<AsyncApexJob> res = new list<AsyncApexJob>();
        for (Integer i = 0; i < 6; i++)  
            res.add(new AsyncApexJob());
        return res;
    }
}

global void execute(SchedulableContext ctx)
{
    try{
        CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime
                                 FROM CronTrigger 
                                 WHERE 
                                 Id = :ctx.getTriggerId()];

        system.debug('--cron expression--'+ct.CronExpression);
        system.debug('-cron trigger run--'+ ct.TimesTriggered);

        LeadReminderMail lrm = new LeadReminderMail();
        lrm.sendMail();

        LeadReminderBatchSMS lrbs = new LeadReminderBatchSMS();

        Integer scopeSize =100;

         if (iGetJobs.getJobs().size()< 5){ 
            Database.executeBatch(lrbs);
         } else {
            //schedule this same schedulable class again in 30 mins

            LeadReminderSchedule lrs = new LeadReminderSchedule();
            Datetime dt = Datetime.now() + (0.024305); // i.e. 30 mins
            String timeForScheduler = dt.format('s m H d M \'?\' yyyy');
            Id schedId = System.Schedule('MatrixRetry'+timeForScheduler,timeForScheduler,lrs);

            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            String[] toAddresses = new String[] {'rohit.k@bmcgroup.in'};
            mail.setToAddresses(toAddresses);
            mail.setSubject('Lead: Reminder Batch Scheduled for later execution' );
            mail.setPlainTextBody
            ('The batch Apex job will be processed at' + dt +
                '. As No. of batches in process are more than 5');
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
        }

        //cleaning(); // to clean deleted or aborted schedule jobs.

    }catch(exception ex){
        system.debug('--Exception is--'+ ex.getMessage());
    }
}

What to add in below test class to cover the else part also (modified):

@isTest
 private class TestLeadReminderSchedule {

public interface IAsyncJob {
        List<AsyncApexJob> getJobs();
}
static testMethod void myUnitTest() {
   test.startTest();
   LeadReminderSchedule lrs = new LeadReminderSchedule();
   String schedule = '0 0 23 * * ?';
   system.schedule('Daily Schedule' , schedule, lrs);
   test.stopTest();
}

static testMethod void myLimitAvoidanceUnitTest() { 
    test.startTest();
    LeadReminderSchedule lrs = new LeadReminderSchedule();
    ***lrs.iGetJobs = new TheOuterClassName.GetTestJobs();***  // will return > 5 asyncjobs and execute your else condition
    String schedule = '0 0 23 * * ?';
    system.schedule('Daily Schedule' , schedule, lrs);
    test.stopTest();

}

}

Above error (Compilation error: Invalid type: TheOuterClassName.GetTestJobs) is coming on this line, in test class : lrs.iGetJobs = new TheOuterClassName.GetTestJobs()
What to do.

Best Answer

Because you can't execute more than one Batch in a testmethod, and, you can't insert AsyncApexJob SObjects in testmethods, you could ...

You could add to your class...

IAsyncJob iGetJobs = new GetProdJobs();  // default to prod interface

 // Interfaces and their implementation - prod vs test
 public interface IAsyncJob {
   List<AsyncApexJob> getJobs();
 }

 public class GetProdJobs implements IAsyncJob {
   public List<AsyncApexJob> getJobs() {
       return [select id FROM AsyncApexJob WHERE JobType='BatchApex' AND (Status = 'Processing' OR Status = 'Preparing' OR Status = 'Queued')];  
   }
 }

 public class GetTestJobs implements IAsyncJob {
    public List<AsyncApexJob> getJobs() {
         List<AsyncApexJob> res = new List<AsyncApexJob> ();
         for (Integer i = 0; i < 6; i++)  
             res.add(new AsyncApexJob());
        return res;
    }
 }

// your logic up to the if statement ...

 if (iGetJobs.getJobs().size()< 5){ 
        Database.executeBatch(lrbs);
    } else {
        //schedule this same schedulable class again in 30 mins
    ....

Then, create two testmethods - one to test the (default) prod interface and one to test the > 5 jobs use case. You already have the first. here's the second

static testMethod void myLimitAvoidanceUnitTest() {
   test.startTest();
   LeadReminderSchedule lrs = new LeadReminderSchedule();
   lrs.iGetJobs = new TheOuterClassName.GetTestJobs();  // will return > 5 asyncjobs and execute your else condition
   String schedule = '0 0 23 * * ?';
   system.schedule('Daily Schedule' , schedule, lrs);
   test.stopTest();
}

There are a couple of other options:

Now, as I mentioned in the comment to the OP, the Appleman pattern avoids this whole limits avoidance routine and is highly recommended.

You could also move all your else logic into a separate method and test that method explicitly to see if it does what you expect to get test coverage

Note - I didn't test the above code as I borrowed it from other working samples and edited it to your use case.

Related Topic