Here's the problem from the docs:
When testing your batch Apex, you can test only one execution of the execute method. You can use the scope parameter of the executeBatch method to limit the number of records passed into the execute method to ensure that you aren't running into governor limits.
Additionally I don't think batch classes are executed from schedulable classes in test classes but I couldn't find that supporting documentation. From my own experience, testing schedulable batch methods only queues the batches while testing the batch method execution actually runs the batch method. So the asserts after your schedulable tests will always fail if they expect a batch job to complete.
Basically, this means you should test the batch and schedulable classes separately. Once you remove the asserts from the schedulable tests cases everything should work fine and it looks like you'll have full coverage.
Also, you only need one '@isTest' line at the top of your test class. I'm also assuming that the schedulable method at the botton of the test class is not a part of that test class but just a mockup of a standalone class used during production. Just in case, here are how the separate classes should be written:
Test Class
@isTest
private class ScheduleBatchTest {
private enum TestCase {
DatabaseExecuteBatch,
ScheduleBatch,
Schedule
}
static void test1() {
execute(TestCase.DatabaseExecuteBatch);
}
static void test2() {
execute(TestCase.ScheduleBatch);
}
static void test3() {
execute(TestCase.Schedule);
}
private static void execute(TestCase tc) {
Account[] accounts = new Account[] {
new Account(Name = 'Acme'),
new Account(Name = 'Nike')
};
insert accounts;
Test.startTest();
if (tc == TestCase.DatabaseExecuteBatch) {
Database.executeBatch(new BatchableExecutorTestBatchable());
Account[] actuals = [select Site from Account where Id in :accounts];
System.assertEquals(accounts.size(), actuals.size());
for (Account actual : actuals) {
System.assertEquals('executed', actual.Site);
}
} else if (tc == TestCase.ScheduleBatch) {
System.scheduleBatch(new BatchableExecutorTestBatchable(),
'my description', 5, 100);
//assert batch job queued here
} else if (tc == TestCase.Schedule) {
System.schedule('my job', '0 0 13 * * ?', new ScheduledBatchable());
//assert scheduled job queued here
}
Test.stopTest();
}
Batch Class:
public class BatchableExecutorTestBatchable implements Database.Batchable<SObject>, Database.Stateful {
public Database.QueryLocator start(Database.BatchableContext bc) {
Database.QueryLocator ql = Database.getQueryLocator([
select Name from Account order by Name
]);
insert new Account(Name = 'start');
return ql;
}
public void execute(Database.BatchableContext bc, List<SObject> scope) {
for (SObject sob : scope) {
Account a = (Account) sob;
a.Site = 'executed';
}
update scope;
insert new Account(Name = 'execute');
}
public void finish(Database.BatchableContext bc) {
insert new Account(Name = 'finish');
}
}
Schedulable Class:
private class ScheduledBatchable implements Schedulable {
public void execute(SchedulableContext sc) {
Database.executeBatch(new BatchableExecutorTestBatchable(), 100);
}
}
Let me know if this works.
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.
Best Answer
You're going to have to fake it. What I do in situations like this is create a property with a getter and setter, but where the setter can only be used in tests.
As an example, setup a new property:
Then change this line:
to
Then in your test you can set *Schedule_Batch.runningJobCount = 5;* in the test you want to test this scenario.