1

I have a Batchable Apex class that queries against an external object in the QueryLocator. I need to be able to write a test against the batch apex class to achieve coverage. I've looked this answer and link which uses the concept of using a method to get query results, but does not use a queryLocator, which I need for the batchable class.

My existing sample POC code works fine when executed from anonymous apex, but I can't seem to get test coverage going.

    global class BatchableLoadPersonsPersisted implements Database.Batchable<SObject> {
    
    global BatchableLoadPersonsPersisted() {
        
    }
    
    /**
     * @description gets invoked when the batch job starts
     * @param context contains the job ID
     * @returns the record set as a QueryLocator object that will be batched for execution
     */ 
    global Database.QueryLocator start(Database.BatchableContext context) {
        return Database.getQueryLocator('SELECT DATEBIRTH__c,DisplayUrl,ExternalId,FIRSTNAME__c,Id,LASTNAME__c,PERSNBR__c,SPOUSEPERSNBR__c FROM Persons__x');
    }

    /**
     * @description gets invoked when the batch job executes and operates on one batch of records. Contains or calls the main execution logic for the batch job.
     * @param context contains the job ID
     * @param scope contains the batch of records to process.
     */ 
    global void execute(Database.BatchableContext context, List<Persons__x> scope) {
        
        List<PersistedPerson__c> persistedPersonsList = new List<PersistedPerson__c>();
        for(Persons__x personx : scope)
        {
            
            PersistedPerson__c persistPerson = new PersistedPerson__c();
            persistPerson.LASTNAME__c = personx.LASTNAME__c;
            persistPerson.FIRSTNAME__c = personx.FIRSTNAME__c;
            persistPerson.DATEBIRTH__c = personx.DATEBIRTH__c;
            persistPerson.PERSONNUMBER__c = personx.PERSNBR__c;
            persistedPersonsList.add(persistPerson);
        }

        upsert persistedPersonsList;
    }
    
    /**
     * @description gets invoked when the batch job finishes. Place any clean up code in this method.
     * @param context contains the job ID
     */ 
    global void finish(Database.BatchableContext context) {
        
    }
}

@isTest
private class BatchableLoadPersonsTests {

    static testmethod void test() {

        // Create test persons to be upserted
        // by the batch job.
        
        Test.startTest();
        Persons__x[] persList = new List<Persons__x>();
        Persons__x person = new Persons__x();
        person.PERSNBR__c = 1;
        person.LASTNAME__c = 'test';
        person.DATEBIRTH__c = Date.newInstance(1960, 2, 17);
        persList.add(person);
        Database.insertImmediate(persList);

         BatchableLoadPersonsPersisted batchLoad = new BatchableLoadPersonsPersisted();
         batchLoad.start(null);
         batchLoad.execute(null, persList);
         batchLoad.finish(null);
        //Database.executeBatch(batchLoad);
        Test.stopTest();

        // Verify persons updated
        PersistedPerson__c[] accUpdatedList = [SELECT Id, LASTNAME__c, FIRSTNAME__c, PERSONNUMBER__c FROM PersistedPerson__c];
        System.assert(!accUpdatedList.isEmpty());
    }
}
10
  • How about... "Oh, you are such a fancy external object you have sooooo much data....NOT!!!" Mar 10 at 17:30
  • What is NOT joke?
    – Kyle J V
    Mar 10 at 17:40
  • 1
    Ok, since I made a joke, I had a look - I think your best bet is to remove as much logic from the execute method as possible and call another private method that does the iteration. This way you get coverage, but don't need to worry about the batchable context. Either that or just call the execute method directly with a hand-built list of external objects. Mar 10 at 18:00
  • So by that approach, you're saying that I'll never get 100% code coverage, but it won't matter because only a few lines are part of the start/execute/finish methods? Will I be able to publish the batchable class if only 1 line is covered though?
    – Kyle J V
    Mar 10 at 19:27
  • You will be able to publish... as long as you have 75% over all classes - or you can aim for 75% for just this class - should be possible using this technique - you are only going to be skipping 1-2 lines Mar 10 at 19:40
1

I think your best bet is to remove as much logic from the execute method as possible and call another private method that does the iteration.

This way you get coverage, but don't need to worry about the batchable context.

eg:

global void execute(Database.BatchableContext context, List<Persons__x> scope) {
     performBatchActions(scope);
} 

@TestVisible
private void performBatchActions(Persons__x[] scope){
  List<PersistedPerson__c> persistedPersonsList = new List<PersistedPerson__c>();
  for(Persons__x personx : scope)
  {
      
      PersistedPerson__c persistPerson = new PersistedPerson__c();
      persistPerson.LASTNAME__c = personx.LASTNAME__c;
      persistPerson.FIRSTNAME__c = personx.FIRSTNAME__c;
      persistPerson.DATEBIRTH__c = personx.DATEBIRTH__c;
      persistPerson.PERSONNUMBER__c = personx.PERSNBR__c;
      persistedPersonsList.add(persistPerson);
  }

  upsert persistedPersonsList;
}

Either that or just call the execute method directly with a hand-built list of external objects.

If you want to deploy the class on its own with its own unit test method, you'll need 75% coverage - this should be possible, despite missing a couple of lines.

If this is not possible, just cover what you can and deploy using run all tests.

2
  • Added a bit of code to make it more useful for others Mar 10 at 20:03
  • 1
    This exposing of a test visible method is unnecessary. Your suggestion to directly call execute (passing null as the context) is far more logical. That said, putting the code from the batch into a separate class that you instantiate and test directly makes sense and is good practice.
    – Phil W
    Mar 10 at 20:42

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.