1

Trying to figure out how to test error condition in the finish() method of this Batchable class. I am testing the error condition in the execute() method by calling it separately in the test and passing null for the BatchableContext parameter. But the finish() method uses the BatchableContext. Is there a way to mock the BatchableContext to test the error condition?

OregonDistrictsBatch.apxc:

global class OregonDistrictsBatch implements Database.Batchable<sObject>, Database.AllowsCallouts, Database.Stateful{

   // instance member to retain state across transactions
    global Integer recordsProcessed = 0;

    // errors stored here for passing to user email in finish method
    Exception[] errors = new Exception[0];
    string stringErrors = '';
    global Boolean apiLimitExceeded = false;

    // query requires oregon street address, active wkr w/PIN or CW record,
    // and null in any one of the 4 fields populated by the callout.
    // for now also including records with 0s in all district fields
    // but can remove that once error handling for over API limits is implemented
    string q = 'SELECT  Id,MailingStreet,MailingCity,MailingState,MailingPostalCode FROM Contact WHERE MailingStreet != NULL AND MailingState=\'OR\' AND ((Active_Worker__c = TRUE AND L503_PIN__c != NULL) OR Has_CareWorks_Record__c = TRUE) AND (District_Update_Date__c = NULL OR Cong_District__c = NULL OR OR_HD__c = NULL OR OR_SD__c = NULL)';

    global Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(q);
    }


   global void execute(Database.BatchableContext BC, List<Contact> scope){
       // process each batch of records
       try {
           for ( Contact c  : scope) { 

                String address = EncodingUtil.urlEncode(c.MailingStreet + ',' 
                                                              + c.MailingCity + ' ' 
                                                              + c.MailingState + ' ' 
                                                              + c.MailingPostalCode, 'UTF-8');
                system.Debug('Address =' + address);

               oregonDistricts od = new oregonDistricts(address);

               if (od.apiLimitExceeded != true) {
                    c.Cong_District__c = od.congressionalDistrict;
                    c.OR_HD__c = od.statehouseDistrict;
                    c.OR_SD__c = od.statesenateDistrict;
                    c.District_Update_Date__c = system.now();
               } else {
                   apiLimitExceeded = true;
                   stringErrors = 'API limit exceeded';
                   return;
               }

                recordsProcessed = recordsProcessed + 1;

            }
                update scope;
       } catch (Exception e) {
           errors.add(e);
       }

    }


   global void finish(Database.BatchableContext BC){
       System.debug(recordsProcessed + ' records were processed.');

       // Get the ID of the AsyncApexJob representing this batch job
       // from Database.BatchableContext.
       // Query the AsyncApexJob object to retrieve the current job's information.
       AsyncApexJob a = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed,
          TotalJobItems, CreatedBy.Email
          FROM AsyncApexJob WHERE Id =
          :BC.getJobId()];

       // Send an email to the Apex job's submitter with list of errors, if any.
       String msg='';
       for (integer i =0; i<errors.size();i++) {
            msg +='\n' +'Error: ' + errors[i];
            }
       if(!errors.isEmpty() || stringErrors != '') {
                Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
                mail.setSubject('Errors occurred during batch process.');
                String[] toAddresses = new String[] {a.CreatedBy.Email};
                mail.setToAddresses(toAddresses);
                mail.setPlainTextBody(msg);
                Messaging.sendEmail(new Messaging.Email[] { mail });
            }
       // Send an email to the Apex job's submitter notifying of job completion.
       Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
       String[] toAddresses = new String[] {a.CreatedBy.Email};
       mail.setToAddresses(toAddresses);
       mail.setSubject('Apex Sharing Recalculation ' + a.Status);
       mail.setPlainTextBody
       ('The batch OregonDistricts job processed ' + recordsProcessed + ' in '  + a.TotalJobItems +
       ' batches with '+ a.NumberOfErrors + ' failures.');
       Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });

    }

}

the uncovered code I'm trying to test is this block in the finish() method: if(!errors.isEmpty() || stringErrors != '') { ... }

This is the test that's working to test the error condition in the execute() method:

static testmethod void testErrorCondition() { 
        List<Contact> contacts = new List<Contact>();
        // insert 10 contacts with unknown employer and test address; these will be guaranteed to meet query criteria for queriedContacts List
        for (Integer i=0;i<10;i++) {
            contacts.add(new Contact(
                firstname='first' + i, 
                lastname='last' + i, 
                accountId='0016100001RyWhAAAV',
                Has_CareWorks_Record__c = TRUE,
                MailingStreet='3892 friar ct se',
                MailingCity='Salem',
                MailingState='OR',
                MailingPostalCode='97302'));
        }
        insert contacts;
        List<Contact> queriedContacts = [SELECT  Id,MailingStreet,MailingCity,MailingState,MailingPostalCode FROM  Contact WHERE MailingStreet != NULL AND (District_Update_Date__c = null OR Cong_District__c  = null OR OR_HD__c = null OR OR_SD__c = null)];
        Test.setMock(HttpCalloutMock.class, new OregonDistrictsErrorMock());
        Test.startTest();
        OregonDistrictsBatch odb = new OregonDistrictsBatch();
        odb.execute(null, queriedContacts);
        Test.stopTest();
        // after the testing stops, assert that 'apiLimitExceeded' var is set to true
        // (this test simulates a callout after daily API limits are met)
        System.assertEquals(odb.apiLimitExceeded, true);
    }

But I need a separate test for the finish() method to test the error condition there and can't figure out how to do it because I need to pass BatchableContext as a parameter.

A commenter on another post suggested using a dependency injection but I don't know how to do that here.

0

I don't think you can mock the BatchableContext.

You can, however, create a private execute method, which then receives the parameters you access with your queries.

If you only wanted to know if there was an error, you could query the job result and pass an integer representing the error count, for example.

global void finish(Database.BatchabelContext bc) {
    // query your unmockable stuff here and pass them as parameters to the
    // next method
    execute(bc /* other simple params as needed*/);
}

@TestVisible
private void execute (Database.BatchabelContext bc, /* other simple params as needed*/) {
    // this method you'll find more testable, since this one you can mock 
    // in your test class, because you can pass the parameters as you want
}

Then in your test class you would be able to create two test methods, one when there are errors, and one when there are no errors:

MyBatchClass c = new MyBatchClass()

// batch context can be null, since we got the information we want from the
// other parameters, in theory

// then we can test for one error
c.execute(null, 1);

// or no errors
c.execute(null, 0);

This would be one kind of dependency injection. You are removing the query logic from inside your method, and passing parameters instead. Parameters that can be set by you manually, and thus being able to mock the method.

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.