Recently I had the opportunity to work with Salesforce’s External Objects and while I created my BATCH class I needed to create a test class that will cover the code. I thought it will be simple but it turned out to be quite hard to make the code coverage. While I was stuck, I saw a lot of people complaining that is quite difficult to mock the data. Therefore, I am providing you the solution.
Here is my opportunity update batch. Here I am updating my opportunities depending on some logic.
//Batch Class - Main class that needs some kind of code coverage
global with sharing class opportunityUpdate_BATCH implements Database.Batchable<sObject>,Database.Stateful{ global String query; global List<ExternalObject__x> externalObjectList; global BATCH_Class (List<sObject> sfList){ init(sfList); } //create your desired query for batch private void init(List<sObject> sfList){ query = '<write your query here>'; //checks if the constructor list is null, list wont be empty in tests. if(sfList== null) { externalObjectList = new List<sObject>(SMT_ExternalObjectQuery.records([ SELECT OpportunityNumber__c FROM ExternalObject__x ])); } else{ externalObjectList= sfList; } }
//starts the batch here and goes through the query global Database.QueryLocator start(Database.BatchableContext BC){ return Database.getQueryLocator(query); } //description calls performBatchActions method.
//Saw some examples saying that if you create another method it will increase code coverage
//while this was true it was not enough. global void execute(Database.BatchableContext BC, List<Opportunity> opportunityList){ performBatchActions(opportunityList); } global void finish(Database.BatchableContext BC){ } //Checks if opportunity has child records for ExternalObject__x and updates the opportunity. //Method created to get more code coverage because of external object private void performBatchActions(Opportunity[] opportunityList){ List<Opportunity> updatedopportunityList = new List<Opportunity>(); List<String> opportunityNumberList = new List<String>(); Map<String,ExternalObject__x> extObjectMap = new Map<String,ExternalObject__x>();
//goes through the opportunity list and adds your specified field in the new list opportunityNumberList for(Opportunity opportunity : opportunityList){ opportunityNumberList.add(opportunity.<AnyField>); }
//goes through the external object list and puts your field as a key to the extObjectMap. for(ExternalObject__x extObject: externalObjectList){
//adds a condition if the field is not empty. if(extObject.<Anyfield> != null){ extObjectMap.put(extObject.<Anyfield>,extObject); } }
//adds a condition if the extObjectMap is not empty. if(extObjectMap.size() > 0){
//goes through the opportunityList for(Opportunity opportunity : opportunityList){ //add your logic here and update opportunity updatedOpportunityList.add(opportunity); } } update updatedOpportunityList; } }
Typically we just set up a simple mock for this type of testing. You can have a class that just passes records through normally, but then when you overwrite it passes whatever you choose:
// Description: Class created for creating mock records for External Objects public virtual inherited sharing class ExternalObjectQuery { public static List<SObject> records(List<SObject> records) { return instance.passThrough(records); } static ExternalObjectQuery instance = new ExternalObjectQuery(); @TestVisible static void setMock(ExternalObjectQuerymock) { instance = mock; } protected virtual List<SObject> passThrough(List<SObject> records) { return records; } }
In your test, usage would then look like this:
// Description: Test class for opportunityUpdate_BATCH @isTest private class opportunityUpdate_BATCH_TEST extends TestFactory { //Overriding the passThrough method from ExternalObjectQuery class Mock extends ExternalObjectQuery { final List<ExternalObject__x > externalRecords; Mock(List<ExternalObject__x > externalRecords) { this.externalRecords = externalRecords; } protected override List<SObject> passThrough(List<SObject> records) { return externalRecords; } } @TestSetup private static void dataSetup()
{ //add what is needed in the TestSetup } @IsTest static void test_OppCustomerCalcValue_BATCH() { List<ExternalObject__x > mockRecords = new List<ExternalObject__x >(); //populate as desired Opportunity oppQuery = <write your query here>; //ExternalObject__x mock record 1 ExternalObject__x mock1 = new ExternalObject__x (); mock1.OpportunityNumber__c = oppQuery.<fieldFromQuery>; //add records to list mockRecords.add(mock1); ExternalObjectQuery.setMock(new Mock(mockRecords)); ExternalObjectQuery.records(mockRecords); Test.startTest(); opportunityUpdate_BATCH oppBatch = new opportunityUpdate_BATCH(mockRecords); Database.executeBatch(oppBatch,200); Test.stopTest(); //make assertions //write your assertion here
} //Test for code coverage of the records and passThrough methods from ExternalObjectQuery
//We're doing this because our usage of the records method in the above test method
//won't cover those methods because they are overridden @IsTest static void test_OppCustomerCalcValue_BATCH_protList() { List<ExternalObject__x > mockRecords = new List<ExternalObject__x >(); //populate as desired call the created opportunity from Test Setup Opportunity oppQuery = <write your query here>; //ExternalObject__x mock record 1 ExternalObject__x mock1 = new ExternalObject__x (); mock1.OpportunityNumber__c = oppQuery.<fieldFromQuery>; //add records to list mockRecords.add(mock1); Test.startTest(); ExternalObjectQuery.records(mockRecords); Test.stopTest(); } }
Want to learn more?
Learn about Difference between Abstract, Virtual and Interface in APEX
Read more about Apex Class Definition
Check out this trailhead about Get Started with Apex Unit Tests
Leave a Reply