I've written a batch class that also implements schedulable. I've written a unit test class and would like to be able to make additional assertions for the results of the batch. The problem is that I can't seem to find a way to determine when the class has completed its execution to hold off doing anything in the unit test long enough to query the actual results.
Below is the code from the schedulable batch classa and the test class I've written:
Schedulable Batch Class:
global class AccountContactRollupBatch extends BatchableTemplate {
/**SCHEDULABLE TEMPLATE METHODS*/
global override void schInit() {
//Get the init vars from Custom Setting
final AutoSchedule__c schJob = AutoSchedule__c.getValues('AutoSchedulable');
super.setAutoSchedule(schJob.autoSchedule__c).
setPeriodicity(schJob.periodicity__c.intValue()).
setDeactivate(schJob.deactivate__c).
setTerminate(schJob.terminate__c).
setSendErrors(schJob.sendErrors__c).
setEmailAddress(schJob.emailAddress__c).
setFromBatchable(schJob.fromBatchable__c).
setParallelExecutions(schJob.parallelExecutions__c);
}
/**BATCHABLE TEMPLATE METHODS*/
global override Database.QueryLocator batchStart(Database.BatchableContext bc) {
this.query = 'Select Id, Number_Of_Contacts__c, (Select Id From Contacts) From Account';
return Database.getQueryLocator(query);
}
global override void batchExecute(Database.BatchableContext bc, List<sObject> scope) {
list<Account>ToUp = new list<Account>();
list<Contact>ActCtcs = new list<Contact>();
integer count = 0;
for(sObject s:scope){
Account a = (Account)s;
ActCtcs = a.Contacts;
for(Contact ac:ActCtcs){
Contact c = new Contact(id = ac.Id );
count ++;
}
a.Number_Of_Contacts__c = count;
system.debug('acctid = ' + a.Id + ' CTC count = ' + a.Number_Of_Contacts__c );
count = 0;
ToUp.add(a);
}
system.debug('ToUp size = ' + ToUp.size());
if(ToUp.isEmpty() == false) update ToUp;
}
global override void batchFinish(Database.BatchableContext bc) {
}
}
Unit Test:
@isTest
public class TestBatchable {
public static testMethod void BatchableTest() {
User testRunner = [Select Id FROM User WHERE Profile.Name = 'System Administrator' limit 1];
system.runAs(testRunner){
// Create 200 test accounts - this simulates one execute.
// Important - the Salesforce.com test framework only allows you to
// test one execute.
If(AutoSchedule__c.getValues('AutoSchedulable') == null){
AutoSchedule__c AutoSchedulable = new AutoSchedule__c();
AutoSchedulable.Name = 'AutoSchedulable';
AutoSchedulable.autoSchedule__c = false;
AutoSchedulable.emailAddress__c = 'crmprogdev@gmail.com';
AutoSchedulable.fromBatchable__c = false;
AutoSchedulable.periodicity__c = 1440;
AutoSchedulable.sendErrors__c = false;
AutoSchedulable.terminate__c = false;
AutoSchedulable.deactivate__c = false;
AutoSchedulable.parallelExecutions__c = false;
upsert AutoSchedulable;
}
List <Account> accns = new List<Account>();
for(integer i = 0; i<200; i++){
Account a = new Account(Name='testAccount'+'i', Ownerid = testRunner.Id );
accns.add(a);
}
insert accns;
// create 5 contacts for each account
list<Contact>Ctcs = new list<Contact>();
for(Account a:accns ){
integer x=0;
for(integer i=0; i<5; i++){
contact c = new contact(accountId = a.Id, OwnerId = a.OwnerId, FirstName = 'Frst_'+ x + '_'+ i , LastName = 'Lst_'+ x + '_'+ i , email = 'tst'+x+'.'+i+'@testbatch.org');
Ctcs.add(c);
}
}
insert Ctcs;
final String minuteString = System.now().addMinutes(1).minute().format();
Schedulable sch = new AccountContactRollupBatch();
final String cron = '05 '+ minuteString + ' * * * ?';
Test.StartTest();
// Schedule the test job
string jobId = System.schedule('AccountContactRollupBatch', cron, sch);
// Get the information from the CronTrigger API object
CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime FROM CronTrigger WHERE id = :jobId];
// Verify the expressions are the same
System.assertEquals(cron, ct.CronExpression);
// Verify the job has not run
System.assertEquals(0, ct.TimesTriggered);
// Verify when the job will run
System.debug('next fire time = '+ ct.NextFireTime.date() + ', '+ ct.NextFireTime.hour() +', '+ ct.NextFireTime.minute());
// Verify the scheduled job hasn't run on the data yet
Account Tacct = [Select Id, Number_Of_Contacts__c FROM Account WHERE OwnerId = :testRunner.Id LIMIT 1];
Boolean Result;
try{
Result = (Tacct.Number_Of_Contacts__c == null);
// system.assert(Result, true);
}catch(nullPointerException e){
system.debug('list exception because Number_of_Contacts__c isNull, Result = true'+ e);
Result = true;
}
system.assert(Result, true);
Test.StopTest();
*** How does one know that a batch has completed? ***
// query for completion
// Verify the next time the job will run
CronTrigger ct2 = [SELECT Id, CronExpression, TimesTriggered, NextFireTime, PreviousFireTime, EndTime, State FROM CronTrigger WHERE id = :jobId];
if(ct2.TimesTriggered >0 && ct2.State == 'Completed'){
System.debug('previous fire time = '+ ct2.NextFireTime.date() + ', End Time '+ ct2.EndTime +', State = '+ ct2.State + ' times triggered = ' + ct2.TimesTriggered );
list<Account>AftrB = [SELECT Id,Number_Of_Contacts__c FROM Account];
*** Line below seems to be the only reliable assertion that can be made ***
system.assertEquals(AftrB.size(), 200);
*** If job isn't complete, sorting through the records won't produce reliable results ***
*** Debug logs always show the job completing, but can't determine when or delay the query ***
for(Account a:AftrB){
system.debug('acctId = '+ a.Id +' num of ctcs = '+ a.Number_Of_Contacts__c);
// system.assertEquals(a.Number_Of_Contacts__c, 5);
}
}//end for loop
}//end runAs
}//end method
}//end class
You'll note in the final code code sections that follow Test.StopTest();
, I'd like to be able to query the results of the batch job AFTER it's finished running. I can see the results in the debug log, but see no obvious way of delaying running the query for the results until the job has completed. Is there a mechanism for doing this?
The only thing I can think of is to give the unt test something "to do" that will "kill time", allowing the job time to complete before querying the results. I'll then be able to do assertions on the results of the batch operations. It seems there must be a better way to do this than creating "busy work tasks" to create idling time in a unit test, but I don't know what that is.
Can someone shed some light on how to deal with this issue?
Code for the abstract classes can be found at Valnavjo's Blog - Batchable Template and Valnavjo's Blog -Schedulable Template along with a more complete explanation of how it works. It uses custom settings to hold and change the intervals for the schedulable's intervals, termination of the schedulable, deactivation, etc.